chore(deps): upgrade @metamask/design-system-react-native to v0.16.0 (design system v31.0.0)#28612
chore(deps): upgrade @metamask/design-system-react-native to v0.16.0 (design system v31.0.0)#28612georgewrmarshall wants to merge 7 commits intomainfrom
Conversation
|
CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes. |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Unsatisfied peer dependency for
@metamask/utils- Updated package.json to require @metamask/utils ^11.11.0 and reinstalled to unify the lockfile resolution.
Or push these changes by commenting:
@cursor push 919d132504
Preview (919d132504)
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -326,7 +326,7 @@
"@metamask/transaction-controller": "^64.0.0",
"@metamask/transaction-pay-controller": "^19.1.0",
"@metamask/tron-wallet-snap": "^1.25.1",
- "@metamask/utils": "^11.8.1",
+ "@metamask/utils": "^11.11.0",
"@myx-trade/sdk": "^0.1.265",
"@ngraveio/bc-ur": "^1.1.6",
"@nktkas/hyperliquid": "^0.30.2",
diff --git a/yarn.lock b/yarn.lock
--- a/yarn.lock
+++ b/yarn.lock
@@ -35686,7 +35686,7 @@
"@metamask/transaction-controller": "npm:^64.0.0"
"@metamask/transaction-pay-controller": "npm:^19.1.0"
"@metamask/tron-wallet-snap": "npm:^1.25.1"
- "@metamask/utils": "npm:^11.8.1"
+ "@metamask/utils": "npm:^11.11.0"
"@myx-trade/sdk": "npm:^0.1.265"
"@ngraveio/bc-ur": "npm:^1.1.6"
"@nktkas/hyperliquid": "npm:^0.30.2"You can send follow-ups to the cloud agent here.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Unused private method
#hasExtendedMarketsForLeagueadded- Removed the unused private method from PolymarketProvider to eliminate dead code.
Or push these changes by commenting:
@cursor push 30c6ac2cbc
Preview (30c6ac2cbc)
diff --git a/app/component-library/components-temp/HeaderSearch/HeaderSearch.tsx b/app/component-library/components-temp/HeaderSearch/HeaderSearch.tsx
--- a/app/component-library/components-temp/HeaderSearch/HeaderSearch.tsx
+++ b/app/component-library/components-temp/HeaderSearch/HeaderSearch.tsx
@@ -23,6 +23,10 @@
} from './HeaderSearch.types';
/**
+ * @deprecated Please update your code to use `HeaderSearch` from `@metamask/design-system-react-native`.
+ * The API may have changed — compare props before migrating.
+ * @see {@link https://github.com/MetaMask/metamask-design-system/blob/main/packages/design-system-react-native/src/components/HeaderSearch/README.md}
+ *
* HeaderSearch is a header component that combines a search field
* with either a back button (screen variant) or cancel button (inline variant).
*
diff --git a/app/component-library/components-temp/KeyValueRow/KeyValueRow.tsx b/app/component-library/components-temp/KeyValueRow/KeyValueRow.tsx
--- a/app/component-library/components-temp/KeyValueRow/KeyValueRow.tsx
+++ b/app/component-library/components-temp/KeyValueRow/KeyValueRow.tsx
@@ -13,6 +13,11 @@
import KeyValueRowRoot from './KeyValueRoot/KeyValueRoot';
/**
+ * @deprecated Please update your code to use `KeyValueRow` from `@metamask/design-system-react-native`.
+ * The API has changed — the new component uses flat props (`keyLabel`, `value`, `variant`) instead of nested `field`/`value` objects.
+ * @see {@link https://github.com/MetaMask/metamask-design-system/blob/main/packages/design-system-react-native/src/components/KeyValueRow/README.md}
+ * @see {@link https://github.com/MetaMask/metamask-design-system/blob/main/packages/design-system-react-native/MIGRATION.md#keyvaluerow-api Migration docs}
+ *
* Prebuilt convenience component to format and render a key/value KeyValueRowLabel pair.
* The KeyValueRowLabel component has props to display a tooltip and icon.
*
diff --git a/app/component-library/components/Avatars/Avatar/Avatar.tsx b/app/component-library/components/Avatars/Avatar/Avatar.tsx
--- a/app/component-library/components/Avatars/Avatar/Avatar.tsx
+++ b/app/component-library/components/Avatars/Avatar/Avatar.tsx
@@ -16,6 +16,12 @@
// Internal dependencies.
import { AvatarProps, AvatarVariant } from './Avatar.types';
+/**
+ * @deprecated Please update your code to use the individual avatar components from `@metamask/design-system-react-native`
+ * such as `AvatarAccount`, `AvatarFavicon`, `AvatarIcon`, `AvatarNetwork`, or `AvatarToken`.
+ * The API may have changed — compare props before migrating.
+ * @see {@link https://github.com/MetaMask/metamask-design-system/blob/main/packages/design-system-react-native/src/components/AvatarAccount/README.md}
+ */
const Avatar = ({ variant, ...props }: AvatarProps) => {
switch (variant) {
case AvatarVariant.Account:
diff --git a/app/component-library/components/Badges/Badge/Badge.tsx b/app/component-library/components/Badges/Badge/Badge.tsx
--- a/app/component-library/components/Badges/Badge/Badge.tsx
+++ b/app/component-library/components/Badges/Badge/Badge.tsx
@@ -16,6 +16,12 @@
BADGE_BADGENOTIFICATIONS_TEST_ID,
} from './Badge.constants';
+/**
+ * @deprecated Please update your code to use the individual badge components from `@metamask/design-system-react-native`
+ * such as `BadgeNetwork`, `BadgeStatus`, `BadgeCount`, or `BadgeIcon`.
+ * The API may have changed — compare props before migrating.
+ * @see {@link https://github.com/MetaMask/metamask-design-system/blob/main/packages/design-system-react-native/src/components/BadgeNetwork/README.md}
+ */
const Badge = ({ variant, ...props }: BadgeProps) => {
switch (variant) {
case BadgeVariant.Network:
diff --git a/app/component-library/components/Badges/Badge/variants/BadgeStatus/BadgeStatus.tsx b/app/component-library/components/Badges/Badge/variants/BadgeStatus/BadgeStatus.tsx
--- a/app/component-library/components/Badges/Badge/variants/BadgeStatus/BadgeStatus.tsx
+++ b/app/component-library/components/Badges/Badge/variants/BadgeStatus/BadgeStatus.tsx
@@ -16,6 +16,11 @@
DEFAULT_BADGESTATUS_STATE,
} from './BadgeStatus.constants';
+/**
+ * @deprecated Please update your code to use `BadgeStatus` from `@metamask/design-system-react-native`.
+ * The API may have changed — compare props before migrating.
+ * @see {@link https://github.com/MetaMask/metamask-design-system/blob/main/packages/design-system-react-native/src/components/BadgeStatus/README.md}
+ */
const BadgeStatus = ({
style,
state = DEFAULT_BADGESTATUS_STATE,
diff --git a/app/component-library/components/Form/TextFieldSearch/TextFieldSearch.tsx b/app/component-library/components/Form/TextFieldSearch/TextFieldSearch.tsx
--- a/app/component-library/components/Form/TextFieldSearch/TextFieldSearch.tsx
+++ b/app/component-library/components/Form/TextFieldSearch/TextFieldSearch.tsx
@@ -13,6 +13,11 @@
import { TEXTFIELDSEARCH_TEST_ID } from './TextFieldSearch.constants';
import styles from './TextFieldSearch.styles';
+/**
+ * @deprecated Please update your code to use `TextFieldSearch` from `@metamask/design-system-react-native`.
+ * The API may have changed — compare props before migrating.
+ * @see {@link https://github.com/MetaMask/metamask-design-system/blob/main/packages/design-system-react-native/src/components/TextFieldSearch/README.md}
+ */
const TextFieldSearch: React.FC<TextFieldSearchProps> = ({
onPressClearButton,
clearButtonProps,
diff --git a/app/component-library/components/Texts/SensitiveText/SensitiveText.tsx b/app/component-library/components/Texts/SensitiveText/SensitiveText.tsx
--- a/app/component-library/components/Texts/SensitiveText/SensitiveText.tsx
+++ b/app/component-library/components/Texts/SensitiveText/SensitiveText.tsx
@@ -5,6 +5,11 @@
// internal dependencies
import { SensitiveTextProps, SensitiveTextLength } from './SensitiveText.types';
+/**
+ * @deprecated Please update your code to use `SensitiveText` from `@metamask/design-system-react-native`.
+ * The API may have changed — compare props before migrating.
+ * @see {@link https://github.com/MetaMask/metamask-design-system/blob/main/packages/design-system-react-native/src/components/SensitiveText/README.md}
+ */
const SensitiveText: React.FC<SensitiveTextProps> = ({
isHidden = false,
children = '',
diff --git a/app/components/UI/Navbar/index.js b/app/components/UI/Navbar/index.js
--- a/app/components/UI/Navbar/index.js
+++ b/app/components/UI/Navbar/index.js
@@ -1130,7 +1130,7 @@
return getHeaderCompactStandardNavbarOptions({
title,
- onClose: () => navigation.getParent()?.pop(),
+ onBack: () => navigation.goBack(),
includesTopInset: true,
});
}
diff --git a/app/components/UI/Navbar/index.test.js b/app/components/UI/Navbar/index.test.js
--- a/app/components/UI/Navbar/index.test.js
+++ b/app/components/UI/Navbar/index.test.js
@@ -696,14 +696,11 @@
});
});
- describe('getBridgeNavbar with getParent', () => {
- it('calls navigation.getParent().pop() when close button is pressed', () => {
- const mockParentPop = jest.fn();
+ describe('getBridgeNavbar back button behavior', () => {
+ it('calls navigation.goBack() when back button is pressed', () => {
const navigationWithParent = {
...mockNavigation,
- getParent: jest.fn(() => ({
- pop: mockParentPop,
- })),
+ getParent: jest.fn(),
};
const options = getBridgeNavbar(
navigationWithParent,
@@ -715,8 +712,8 @@
const Header = options.header;
const { getByTestId } = render(<Header />);
fireEvent.press(getByTestId('button-icon'));
- expect(navigationWithParent.getParent).toHaveBeenCalled();
- expect(mockParentPop).toHaveBeenCalled();
+ expect(navigationWithParent.goBack).toHaveBeenCalled();
+ expect(navigationWithParent.getParent).not.toHaveBeenCalled();
});
});
diff --git a/app/components/UI/Predict/constants/flags.ts b/app/components/UI/Predict/constants/flags.ts
--- a/app/components/UI/Predict/constants/flags.ts
+++ b/app/components/UI/Predict/constants/flags.ts
@@ -1,4 +1,5 @@
import {
+ PredictExtendedSportsMarketsFlag,
PredictFeeCollection,
PredictHotTabFlag,
PredictLiveSportsFlag,
@@ -23,6 +24,13 @@
leagues: [],
};
+export const DEFAULT_EXTENDED_SPORTS_MARKETS_FLAG: PredictExtendedSportsMarketsFlag =
+ {
+ enabled: false,
+ minimumVersion: '',
+ leagues: [],
+ };
+
export const DEFAULT_MARKET_HIGHLIGHTS_FLAG: PredictMarketHighlightsFlag = {
enabled: false,
highlights: [],
diff --git a/app/components/UI/Predict/providers/polymarket/PolymarketProvider.test.ts b/app/components/UI/Predict/providers/polymarket/PolymarketProvider.test.ts
--- a/app/components/UI/Predict/providers/polymarket/PolymarketProvider.test.ts
+++ b/app/components/UI/Predict/providers/polymarket/PolymarketProvider.test.ts
@@ -252,6 +252,7 @@
const defaultFeatureFlags: PredictFeatureFlags = {
feeCollection: DEFAULT_FEE_COLLECTION_FLAG,
liveSportsLeagues: [],
+ extendedSportsMarketsLeagues: [],
marketHighlightsFlag: {
enabled: false,
highlights: [],
diff --git a/app/components/UI/Predict/selectors/featureFlags/index.test.ts b/app/components/UI/Predict/selectors/featureFlags/index.test.ts
--- a/app/components/UI/Predict/selectors/featureFlags/index.test.ts
+++ b/app/components/UI/Predict/selectors/featureFlags/index.test.ts
@@ -1,4 +1,5 @@
import {
+ selectExtendedSportsMarketsLeagues,
selectPredictEnabledFlag,
selectPredictFakOrdersEnabledFlag,
selectPredictFeatureFlags,
@@ -1255,4 +1256,122 @@
expect(result).toBe(false);
});
});
+
+ describe('selectExtendedSportsMarketsLeagues', () => {
+ it('returns leagues when flag is enabled and version check passes', () => {
+ mockHasMinimumRequiredVersion.mockReturnValue(true);
+ const state = {
+ engine: {
+ backgroundState: {
+ RemoteFeatureFlagController: {
+ remoteFeatureFlags: {
+ predictExtendedSportsMarkets: {
+ enabled: true,
+ minimumVersion: '1.0.0',
+ leagues: ['nba', 'ucl'],
+ },
+ },
+ cacheTimestamp: 0,
+ },
+ },
+ },
+ };
+
+ const result = selectExtendedSportsMarketsLeagues(state);
+
+ expect(result).toEqual(['nba', 'ucl']);
+ });
+
+ it('returns empty array when flag is disabled', () => {
+ mockHasMinimumRequiredVersion.mockReturnValue(true);
+ const state = {
+ engine: {
+ backgroundState: {
+ RemoteFeatureFlagController: {
+ remoteFeatureFlags: {
+ predictExtendedSportsMarkets: {
+ enabled: false,
+ minimumVersion: '1.0.0',
+ leagues: ['nba', 'ucl'],
+ },
+ },
+ cacheTimestamp: 0,
+ },
+ },
+ },
+ };
+
+ const result = selectExtendedSportsMarketsLeagues(state);
+
+ expect(result).toEqual([]);
+ });
+
+ it('returns empty array when app version is below minimum required version', () => {
+ mockHasMinimumRequiredVersion.mockReturnValue(false);
+ const state = {
+ engine: {
+ backgroundState: {
+ RemoteFeatureFlagController: {
+ remoteFeatureFlags: {
+ predictExtendedSportsMarkets: {
+ enabled: true,
+ minimumVersion: '99.0.0',
+ leagues: ['nba'],
+ },
+ },
+ cacheTimestamp: 0,
+ },
+ },
+ },
+ };
+
+ const result = selectExtendedSportsMarketsLeagues(state);
+
+ expect(result).toEqual([]);
+ });
+
+ it('returns empty array when remote feature flags are empty', () => {
+ const result = selectExtendedSportsMarketsLeagues(mockedEmptyFlagsState);
+
+ expect(result).toEqual([]);
+ });
+
+ it('returns empty array when controller is undefined', () => {
+ const state = {
+ engine: {
+ backgroundState: {
+ RemoteFeatureFlagController: undefined,
+ },
+ },
+ };
+
+ const result = selectExtendedSportsMarketsLeagues(state);
+
+ expect(result).toEqual([]);
+ });
+
+ it('filters out unsupported leagues', () => {
+ mockHasMinimumRequiredVersion.mockReturnValue(true);
+ const state = {
+ engine: {
+ backgroundState: {
+ RemoteFeatureFlagController: {
+ remoteFeatureFlags: {
+ predictExtendedSportsMarkets: {
+ enabled: true,
+ minimumVersion: '1.0.0',
+ leagues: ['nba', 'fake_league', 'ucl'],
+ },
+ },
+ cacheTimestamp: 0,
+ },
+ },
+ },
+ };
+
+ const result = selectExtendedSportsMarketsLeagues(state);
+
+ expect(result).toEqual(['nba', 'ucl']);
+ });
+ });
});
diff --git a/app/components/UI/Predict/selectors/featureFlags/index.ts b/app/components/UI/Predict/selectors/featureFlags/index.ts
--- a/app/components/UI/Predict/selectors/featureFlags/index.ts
+++ b/app/components/UI/Predict/selectors/featureFlags/index.ts
@@ -122,6 +122,11 @@
resolvePredictFeatureFlags({ remoteFeatureFlags, localOverrides }),
);
+export const selectExtendedSportsMarketsLeagues = createSelector(
+ selectPredictFeatureFlags,
+ (flags) => flags.extendedSportsMarketsLeagues,
+);
+
export const selectPredictFeeCollectionFlag = createSelector(
selectPredictFeatureFlags,
(flags) => flags.feeCollection,
diff --git a/app/components/UI/Predict/types/flags.ts b/app/components/UI/Predict/types/flags.ts
--- a/app/components/UI/Predict/types/flags.ts
+++ b/app/components/UI/Predict/types/flags.ts
@@ -18,9 +18,15 @@
highlights: PredictMarketHighlight[];
}
+export interface PredictExtendedSportsMarketsFlag
+ extends VersionGatedFeatureFlag {
+ leagues: string[];
+}
+
export interface PredictFeatureFlags {
feeCollection: PredictFeeCollection;
liveSportsLeagues: string[];
+ extendedSportsMarketsLeagues: string[];
marketHighlightsFlag: PredictMarketHighlightsFlag;
fakOrdersEnabled: boolean;
predictWithAnyTokenEnabled: boolean;
diff --git a/app/components/UI/Predict/utils/resolvePredictFeatureFlags.test.ts b/app/components/UI/Predict/utils/resolvePredictFeatureFlags.test.ts
--- a/app/components/UI/Predict/utils/resolvePredictFeatureFlags.test.ts
+++ b/app/components/UI/Predict/utils/resolvePredictFeatureFlags.test.ts
@@ -1,5 +1,6 @@
import { validatedVersionGatedFeatureFlag } from '../../../../util/remoteFeatureFlag';
import {
+ DEFAULT_EXTENDED_SPORTS_MARKETS_FLAG,
DEFAULT_FEE_COLLECTION_FLAG,
DEFAULT_MARKET_HIGHLIGHTS_FLAG,
} from '../constants/flags';
@@ -25,6 +26,7 @@
expect(result).toEqual({
feeCollection: DEFAULT_FEE_COLLECTION_FLAG,
liveSportsLeagues: [],
+ extendedSportsMarketsLeagues: [],
marketHighlightsFlag: DEFAULT_MARKET_HIGHLIGHTS_FLAG,
fakOrdersEnabled: false,
predictWithAnyTokenEnabled: false,
@@ -183,4 +185,123 @@
expect(result.fakOrdersEnabled).toBe(true);
expect(result.predictWithAnyTokenEnabled).toBe(false);
});
+
+ describe('extendedSportsMarketsLeagues', () => {
+ it('returns empty array when flag is missing', () => {
+ const result = resolvePredictFeatureFlags({});
+
+ expect(result.extendedSportsMarketsLeagues).toEqual([]);
+ });
+
+ it('returns empty array when flag is disabled', () => {
+ const result = resolvePredictFeatureFlags({
+ remoteFeatureFlags: {
+ predictExtendedSportsMarkets: {
+ ...DEFAULT_EXTENDED_SPORTS_MARKETS_FLAG,
+ enabled: false,
+ leagues: ['nba', 'ucl'],
+ },
+ },
+ });
+
+ expect(result.extendedSportsMarketsLeagues).toEqual([]);
+ });
+
+ it('returns empty array when version check fails', () => {
+ mockValidatedVersionGatedFeatureFlag.mockImplementation((flag) => {
+ if (flag && typeof flag === 'object' && 'leagues' in flag) {
+ return false;
+ }
+ return undefined;
+ });
+
+ const result = resolvePredictFeatureFlags({
+ remoteFeatureFlags: {
+ predictExtendedSportsMarkets: {
+ enabled: true,
+ minimumVersion: '99.0.0',
+ leagues: ['nba', 'ucl'],
+ },
+ },
+ });
+
+ expect(result.extendedSportsMarketsLeagues).toEqual([]);
+ });
+
+ it('returns filtered leagues when enabled and version check passes', () => {
+ mockValidatedVersionGatedFeatureFlag.mockImplementation((flag) => {
+ if (flag && typeof flag === 'object' && 'leagues' in flag) {
+ return true;
+ }
+ return undefined;
+ });
+
+ const result = resolvePredictFeatureFlags({
+ remoteFeatureFlags: {
+ predictExtendedSportsMarkets: {
+ enabled: true,
+ minimumVersion: '1.0.0',
+ leagues: ['nba', 'ucl', 'fake_league'],
+ },
+ },
+ });
+
+ expect(result.extendedSportsMarketsLeagues).toEqual(['nba', 'ucl']);
+ });
+
+ it('unwraps progressive rollout shape', () => {
+ mockValidatedVersionGatedFeatureFlag.mockImplementation((flag) => {
+ if (flag && typeof flag === 'object' && 'leagues' in flag) {
+ return true;
+ }
+ return undefined;
+ });
+
+ const result = resolvePredictFeatureFlags({
+ remoteFeatureFlags: {
+ predictExtendedSportsMarkets: {
+ name: 'group-a',
+ value: {
+ enabled: true,
+ minimumVersion: '1.0.0',
+ leagues: ['nba', 'epl'],
+ },
+ },
+ },
+ });
+
+ expect(result.extendedSportsMarketsLeagues).toEqual(['nba', 'epl']);
+ });
+
+ it('applies local override over remote flag', () => {
+ mockValidatedVersionGatedFeatureFlag.mockImplementation((flag) =>
+ Boolean(
+ flag &&
+ typeof flag === 'object' &&
+ 'enabled' in flag &&
+ 'leagues' in flag &&
+ (flag as { enabled: boolean }).enabled,
+ ),
+ );
+
+ const result = resolvePredictFeatureFlags({
+ remoteFeatureFlags: {
+ predictExtendedSportsMarkets: {
+ enabled: true,
+ minimumVersion: '1.0.0',
+ leagues: ['nba', 'ucl'],
+ },
+ },
+ localOverrides: {
+ predictExtendedSportsMarkets: {
+ enabled: false,
+ minimumVersion: '1.0.0',
+ leagues: ['nba', 'ucl'],
+ },
+ },
+ });
+
+ expect(result.extendedSportsMarketsLeagues).toEqual([]);
+ });
+ });
});
diff --git a/app/components/UI/Predict/utils/resolvePredictFeatureFlags.ts b/app/components/UI/Predict/utils/resolvePredictFeatureFlags.ts
--- a/app/components/UI/Predict/utils/resolvePredictFeatureFlags.ts
+++ b/app/components/UI/Predict/utils/resolvePredictFeatureFlags.ts
@@ -3,6 +3,7 @@
validatedVersionGatedFeatureFlag,
} from '../../../../util/remoteFeatureFlag';
import {
+ DEFAULT_EXTENDED_SPORTS_MARKETS_FLAG,
DEFAULT_FEE_COLLECTION_FLAG,
DEFAULT_LIVE_SPORTS_FLAG,
DEFAULT_MARKET_HIGHLIGHTS_FLAG,
@@ -10,6 +11,7 @@
import { filterSupportedLeagues } from '../constants/sports';
import { parse, PredictFeeCollectionSchema } from '../schemas';
import {
+ PredictExtendedSportsMarketsFlag,
PredictFeatureFlags,
PredictLiveSportsFlag,
PredictMarketHighlightsFlag,
@@ -80,9 +82,23 @@
unwrapRemoteFeatureFlag<VersionGatedFeatureFlag>(flags.predictUpDown),
) ?? false;
+ const rawExtendedSportsFlag =
+ unwrapRemoteFeatureFlag<PredictExtendedSportsMarketsFlag>(
+ flags.predictExtendedSportsMarkets,
+ ) ?? DEFAULT_EXTENDED_SPORTS_MARKETS_FLAG;
+
+ const isExtendedSportsEnabled = validatedVersionGatedFeatureFlag(
+ rawExtendedSportsFlag,
+ );
+
+ const extendedSportsMarketsLeagues = isExtendedSportsEnabled
+ ? filterSupportedLeagues(rawExtendedSportsFlag.leagues ?? [])
+ : [];
+
return {
feeCollection,
liveSportsLeagues,
+ extendedSportsMarketsLeagues,
marketHighlightsFlag,
fakOrdersEnabled,
predictWithAnyTokenEnabled,
diff --git a/app/components/Views/Homepage/Sections/Predictions/PredictionsSection.test.tsx b/app/components/Views/Homepage/Sections/Predictions/PredictionsSection.test.tsx
--- a/app/components/Views/Homepage/Sections/Predictions/PredictionsSection.test.tsx
+++ b/app/components/Views/Homepage/Sections/Predictions/PredictionsSection.test.tsx
@@ -4,6 +4,7 @@
import PredictionsSection from './PredictionsSection';
import Routes from '../../../../../constants/navigation/Routes';
import { PREDICT_CLAIM_BUTTON_TEST_IDS } from '../../../../UI/Predict/components/PredictActionButtons/PredictClaimButton.testIds';
+import { PredictEventValues } from '../../../../UI/Predict/constants/eventNames';
const mockNavigate = jest.fn();
const mockClaim = jest.fn();
@@ -234,6 +235,33 @@
});
});
+ it('navigates with homepage_positions entry_point when positions section title is pressed', () => {
+ mockUsePredictPositionsForHomepage.mockImplementation(
+ ({
+ claimable = false,
+ }: { maxPositions?: number; claimable?: boolean } = {}) => ({
+ positions: claimable ? [] : mockActivePositions,
+ isLoading: false,
+ error: null,
+ totalClaimableValue: 0,
+ refetch: jest.fn(),
+ }),
+ );
+
+ renderWithProvider(
+ <PredictionsSection sectionIndex={0} totalSectionsLoaded={1} />,
+ );
+
+ fireEvent.press(screen.getByText('Predictions'));
+
+ expect(mockNavigate).toHaveBeenCalledWith(Routes.PREDICT.ROOT, {
+ screen: Routes.PREDICT.MARKET_LIST,
+ params: {
+ entryPoint: PredictEventValues.ENTRY_POINT.HOMEPAGE_POSITIONS,
+ },
+ });
+ });
+
it('returns null when predict is disabled', () => {
jest
.requireMock('../../../../UI/Predict/selectors/featureFlags')
@@ -639,6 +667,32 @@
expect(screen.getByText('Test Position 1')).toBeOnTheScreen();
});
+ it('navigates with homepage_positions entry_point on title press', () => {
+ mockUsePredictPositionsForHomepage.mockReturnValue({
+ positions: mockActivePositions,
+ isLoading: false,
+ error: null,
+ refetch: jest.fn(),
+ });
+
+ renderWithProvider(
+ <PredictionsSection
+ sectionIndex={0}
+ totalSectionsLoaded={5}
+ mode="positions-only"
+ />,
+ );
+
+ fireEvent.press(screen.getByText('Predictions'));
+
+ expect(mockNavigate).toHaveBeenCalledWith(Routes.PREDICT.ROOT, {
+ screen: Routes.PREDICT.MARKET_LIST,
+ params: {
+ entryPoint: PredictEventValues.ENTRY_POINT.HOMEPAGE_POSITIONS,
+ },
+ });
+ });
+
it('returns null when no positions after loading', () => {
mockUsePredictPositionsForHomepage.mockReturnValue({
positions: [],
diff --git a/app/components/Views/Homepage/Sections/Predictions/PredictionsSection.tsx b/app/components/Views/Homepage/Sections/Predictions/PredictionsSection.tsx
--- a/app/components/Views/Homepage/Sections/Predictions/PredictionsSection.tsx
+++ b/app/components/Views/Homepage/Sections/Predictions/PredictionsSection.tsx
@@ -242,6 +242,15 @@
});
}, [navigation]);
+ const handleViewAllFromPositions = useCallback(() => {
+ navigation.navigate(Routes.PREDICT.ROOT, {
+ screen: Routes.PREDICT.MARKET_LIST,
+ params: {
+ entryPoint: PredictEventValues.ENTRY_POINT.HOMEPAGE_POSITIONS,
+ },
+ });
+ }, [navigation]);
+
const handlePositionPress = useCallback(
(position: PredictPosition) => {
navigation.navigate(Routes.PREDICT.ROOT, {
@@ -256,7 +265,11 @@
[navigation],
);
- return { handleViewAllPredictions, handlePositionPress };
+ return {
+ handleViewAllPredictions,
+ handleViewAllFromPositions,
+ handlePositionPress,
+ };
};
const usePredictPositionsSectionData = () => {
@@ -334,9 +347,12 @@
const queryClient = useQueryClient();
const title = titleOverride ?? strings('homepage.sections.predictions');
const analyticsName = sectionNameOverride ?? HomeSectionNames.PREDICT;
- const { handleViewAllPredictions, handlePositionPress } =
- usePredictNavigationHandlers();
const {
+ handleViewAllPredictions,
+ handleViewAllFromPositions,
+ handlePositionPress,
+ } = usePredictNavigationHandlers();
+ const {
privacyMode,
positions,
isLoadingPositions,
@@ -406,7 +422,7 @@
<View ref={sectionViewRef} onLayout={onLayout}>
<HomepagePredictPositions
title={title}
- onViewAll={handleViewAllPredictions}
+ onViewAll={handleViewAllFromPositions}
privacyMode={privacyMode}
isLoadingPositions={isLoadingPositions}
positions={positions}
@@ -456,7 +472,7 @@
const queryClient = useQueryClient();
const title = titleOverride ?? strings('homepage.sections.predictions');
const analyticsName = sectionNameOverride ?? HomeSectionNames.PREDICT;
- const { handleViewAllPredictions, handlePositionPress } =
+ const { handleViewAllFromPositions, handlePositionPress } =
usePredictNavigationHandlers();
const {
privacyMode,
@@ -503,7 +519,7 @@
<View ref={sectionViewRef} onLayout={onLayout}>
<HomepagePredictPositions
title={title}
- onViewAll={handleViewAllPredictions}
+ onViewAll={handleViewAllFromPositions}
privacyMode={privacyMode}
isLoadingPositions={isLoadingPositions}
positions={positions}
diff --git a/app/components/Views/confirmations/hooks/useAutomaticGasFeeTokenSelect.test.ts b/app/components/Views/confirmations/hooks/useAutomaticGasFeeTokenSelect.test.ts
--- a/app/components/Views/confirmations/hooks/useAutomaticGasFeeTokenSelect.test.ts
+++ b/app/components/Views/confirmations/hooks/useAutomaticGasFeeTokenSelect.test.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/naming-convention */
import { GasFeeToken, TransactionMeta } from '@metamask/transaction-controller';
import { Hex } from '@metamask/utils';
import { act } from '@testing-library/react';
@@ -19,6 +20,13 @@
jest.mock('../../../../util/transaction-controller');
jest.mock('./gas/useIsGaslessSupported');
+const mockSetConfirmationMetric = jest.fn();
+jest.mock('./metrics/useConfirmationMetricEvents', () => ({
+ useConfirmationMetricEvents: () => ({
+ setConfirmationMetric: mockSetConfirmationMetric,
+ }),
+}));
+
const FROM_MOCK = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc';
export const GAS_FEE_TOKEN_MOCK: GasFeeToken = {
amount: toHex(1000),
@@ -128,11 +136,19 @@
expect.any(String),
GAS_FEE_TOKEN_MOCK.tokenAddress,
);
+ expect(mockSetConfirmationMetric).toHaveBeenCalledTimes(1);
+ expect(mockSetConfirmationMetric).toHaveBeenCalledWith({
+ properties: {
+ gas_payment_token_default: true,
+ gas_payment_token_default_symbol: GAS_FEE_TOKEN_MOCK.symbol,
+ },
+ });
});
it('does not select first gas fee token if gas fee token already selected', () => {
runHook({ selectedGasFeeToken: GAS_FEE_TOKEN_MOCK.tokenAddress });
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
+ expect(mockSetConfirmationMetric).not.toHaveBeenCalled();
});
it('selects first gas fee token if gas fee token already selected but doesnt correspond to any gasFeeTokens (only if `excludeNativeTokenForFee` is set', () => {
@@ -155,11 +171,19 @@
expect.any(String),
'0x9876543210000000000000000000000000000000',
);
+ expect(mockSetConfirmationMetric).toHaveBeenCalledTimes(1);
+ expect(mockSetConfirmationMetric).toHaveBeenCalledWith({
+ properties: {
+ gas_payment_token_default: true,
+ gas_payment_token_default_symbol: undefined,
+ },
+ });
});
it('does not select first gas fee token if no gas fee tokens', () => {
runHook({ gasFeeTokens: [] });
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
+ expect(mockSetConfirmationMetric).not.toHaveBeenCalled();
});
it('does not select first gas fee token if not first load', () => {
@@ -177,6 +201,7 @@
rerender({});
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
+ expect(mockSetConfirmationMetric).not.toHaveBeenCalled();
});
it('does not select first gas fee token if gasless not supported', () => {
@@ -189,11 +214,13 @@
runHook();
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
+ expect(mockSetConfirmationMetric).not.toHaveBeenCalled();
});
it('does not select first gas fee token if sufficient balance', () => {
runHook();
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
+ expect(mockSetConfirmationMetric).not.toHaveBeenCalled();
});
it('does not select first gas fee token after firstCheck is set to false', () => {
@@ -210,11 +237,13 @@
});
rerender({});
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(1); // Only first run
+ expect(mockSetConfirmationMetric).toHaveBeenCalledTimes(1); // Only first run
});
it('does not select if gasFeeTokens is falsy', () => {
runHook({ gasFeeTokens: [] });
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
+ expect(mockSetConfirmationMetric).not.toHaveBeenCalled();
... diff truncated: showing 800 of 986 linesYou can send follow-ups to the cloud agent here.
eb83a86 to
dda4e20
Compare
| "@metamask/delegation-controller": "^2.0.2", | ||
| "@metamask/delegation-deployments": "^1.0.0", | ||
| "@metamask/design-system-react-native": "^0.13.0", | ||
| "@metamask/design-system-react-native": "^0.16.0", |
There was a problem hiding this comment.
Why ^0.16.0?
This bumps the design system monorepo from v0.13.0 to v0.16.0 (monorepo release v31.0.0). The codebase was audited for breaking changes before upgrading: BoxHorizontal→BoxRow and BoxVertical→BoxColumn renames have no usages here, and the KeyValueRow API overhaul doesn't affect this repo since it uses a local implementation in components-temp/.
| "@metamask/transaction-pay-controller": "^19.1.0", | ||
| "@metamask/tron-wallet-snap": "^1.25.1", | ||
| "@metamask/utils": "^11.8.1", | ||
| "@metamask/utils": "^11.11.0", |
There was a problem hiding this comment.
Why bump @metamask/utils here?
@metamask/design-system-react-native v0.16.0 declares a peer dependency on @metamask/utils: ^11.11.0, but the project was pinned to ^11.8.1 (resolving to 11.10.0). Without this bump, npm/yarn resolves two separate copies of @metamask/utils in the tree — one for the host (11.10.0) and one for @metamask/design-system-shared (11.11.0) — which can cause instanceof checks to silently fail across module boundaries.
| import KeyValueRowRoot from './KeyValueRoot/KeyValueRoot'; | ||
|
|
||
| /** | ||
| * @deprecated Please update your code to use `KeyValueRow` from `@metamask/design-system-react-native`. |
There was a problem hiding this comment.
Breaking API change — not a drop-in replacement.
The DSRN KeyValueRow v0.16.0 completely replaced the nested field/value object API with flat props: keyLabel, value, variant, keyEndButtonIconProps, etc. The local implementation here (and its KeyValueRowStubs sub-components) serves ~15 call sites across the codebase that rely on the old shape. Migration requires updating each call site to the new flat-prop API — see the linked migration docs.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Broad axios preapproval bypasses age gate permanently
- Replaced broad 'axios' preapproval with 'axios@1.15.0' to limit the bypass to the intended SSRF-fix release and restore the age gate for all other versions.
Or push these changes by commenting:
@cursor push 1972fe3f39
Preview (1972fe3f39)
diff --git a/.yarnrc.yml b/.yarnrc.yml
--- a/.yarnrc.yml
+++ b/.yarnrc.yml
@@ -30,4 +30,4 @@
- '@metamask-previews/*'
- '@lavamoat/*'
- '@consensys/*'
- - 'axios' # Preapproved to allow 1.15.0 (critical SSRF fix) before 3-day age gate expires
+ - 'axios@1.15.0' # Preapproved to allow 1.15.0 (critical SSRF fix) before 3-day age gate expiresYou can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit 6f9d24e. Configure here.
6f9d24e to
588124d
Compare
| * @see {@link https://github.com/MetaMask/metamask-design-system/blob/main/packages/design-system-react-native/src/components/AvatarIcon/README.md AvatarIcon} | ||
| * @see {@link https://github.com/MetaMask/metamask-design-system/blob/main/packages/design-system-react-native/src/components/AvatarNetwork/README.md AvatarNetwork} | ||
| * @see {@link https://github.com/MetaMask/metamask-design-system/blob/main/packages/design-system-react-native/src/components/AvatarToken/README.md AvatarToken} | ||
| */ |
There was a problem hiding this comment.
Avatar should be replaced with one of the variants we no longer have this unified component
fc934d7 to
664a12a
Compare
664a12a to
76603c0
Compare
74d0994 to
5740e79
Compare
| import { Box } from '@metamask/design-system-react-native'; | ||
| import Icon, { | ||
| IconName, | ||
| IconSize, |
There was a problem hiding this comment.
Fixing wrong icon size import
🔍 Smart E2E Test Selection
click to see 🤖 AI reasoning detailsE2E Test Selection: The PR bumps @metamask/design-system-react-native from ^0.14.0 to ^0.16.0 (2 minor versions) and @metamask/utils from ^11.8.1 to ^11.11.0. The snapshot changes confirm real rendering differences from the design system update:
The deprecated JSDoc additions (Avatar, Badge, BadgeStatus, TextFieldSearch, HeaderSearch, KeyValueRow, SensitiveText, KeyValueRow) are documentation-only with zero functional impact. However, the design system package update affects widely-used components across the app. The snapshot changes indicate visual rendering differences that could affect:
Not selecting all tags because: the changes are primarily design system rendering updates (not logic changes), the deprecated annotations are documentation-only, and the core functionality of most features is unaffected. The @metamask/utils bump is a utility library update unlikely to break E2E flows. Dependent tag requirements checked:
Performance Test Selection: |
|
✅ E2E Fixture Validation — Schema is up to date |


Description
Upgrades
@metamask/design-system-react-nativefrom^0.13.0to^0.16.0, corresponding to the MetaMask Design System v31.0.0 release.What changed in v31.0.0
New components available:
HeaderSearch— search header component (we have a local version; the DSRN version can be evaluated for future migration)KeyValueColumn— vertical key/value layout componentBreaking changes audited:
BoxHorizontal→BoxRowBoxVertical→BoxColumnBoxHorizontalPropsShared→BoxRowPropsSharedBoxVerticalPropsShared→BoxColumnPropsSharedKeyValueRowstub-based composition removedKeyValueRowincomponents-temp/— not affectedChangelog
CHANGELOG entry: null
Related issues
Fixes:
Manual testing steps
Screenshots/Recordings
Before
After
Pre-merge author checklist
Pre-merge reviewer checklist
Note
Medium Risk
Upgrades a core UI dependency, which can subtly change component behavior/styling across the app despite minimal local code changes. Snapshot updates indicate expected rendering/prop differences (e.g., icon sizing, tailwind class output).
Overview
Upgrades
@metamask/design-system-react-nativeto^0.16.0(and@metamask/utilsto^11.11.0), updating the lockfile accordingly.Adds deprecation notices and migration links to several in-repo component-library wrappers (
Avatar,Badge,BadgeStatus,TextFieldSearch,SensitiveText, and tempHeaderSearch/KeyValueRow) to steer consumers toward the design-system equivalents.Adjusts usage/tests to match the new design-system outputs:
usePredictShare.utilsnow sourcesIconSizefrom the localIconcomponent, and Jest snapshots are updated for icon size tokens (e.g.,"24"→"lg") and tailwind class formatting (e.g.,w-[32px] h-[32px]→w-8 h-8).Reviewed by Cursor Bugbot for commit 5740e79. Bugbot is set up for automated code reviews on this repo. Configure here.