Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/github": "^6.0.0",
"axios": "^1.13.5",
"axios": "^1.15.0",
"simple-git": "^3.25.0"
},
"devDependencies": {
Expand Down
20 changes: 10 additions & 10 deletions .github/scripts/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ __metadata:
"@lavamoat/allow-scripts": "npm:^3.2.0"
"@lavamoat/preinstall-always-fail": "npm:^2.1.0"
"@types/node": "npm:^20.16.2"
axios: "npm:^1.13.5"
axios: "npm:^1.15.0"
simple-git: "npm:^3.25.0"
ts-node: "npm:^10.5.0"
typescript: "npm:~5.4.5"
Expand Down Expand Up @@ -508,14 +508,14 @@ __metadata:
languageName: node
linkType: hard

"axios@npm:^1.13.5":
version: 1.13.5
resolution: "axios@npm:1.13.5"
"axios@npm:^1.15.0":
version: 1.15.0
resolution: "axios@npm:1.15.0"
dependencies:
follow-redirects: "npm:^1.15.11"
form-data: "npm:^4.0.5"
proxy-from-env: "npm:^1.1.0"
checksum: 10/db726d09902565ef9a0632893530028310e2ec2b95b727114eca1b101450b00014133dfc3871cffc87983fb922bca7e4874d7e2826d1550a377a157cdf3f05b6
proxy-from-env: "npm:^2.1.0"
checksum: 10/d39a2c0ebc7ff4739401b282e726cc2673377949d6c46d60eb619458f8d7a2f7eadbcada7097f4dbc7d5c59abb4d3bf6fac33d474412bc3415d3f5aa7ed45530
languageName: node
linkType: hard

Expand Down Expand Up @@ -1422,10 +1422,10 @@ __metadata:
languageName: node
linkType: hard

"proxy-from-env@npm:^1.1.0":
version: 1.1.0
resolution: "proxy-from-env@npm:1.1.0"
checksum: 10/f0bb4a87cfd18f77bc2fba23ae49c3b378fb35143af16cc478171c623eebe181678f09439707ad80081d340d1593cd54a33a0113f3ccb3f4bc9451488780ee23
"proxy-from-env@npm:^2.1.0":
version: 2.1.0
resolution: "proxy-from-env@npm:2.1.0"
checksum: 10/fbbaf4dab2a6231dc9e394903a5f66f20475e36b734335790b46feb9da07c37d6b32e2c02e3e2ea4d4b23774c53d8562e5b7cc73282cb43f4a597b7eacaee2ee
languageName: node
linkType: hard

Expand Down
1 change: 1 addition & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ npmPreapprovedPackages:
- '@metamask-previews/*'
- '@lavamoat/*'
- '@consensys/*'
- 'axios' # TODO: Remove after 2025-04-12 once axios 1.15.0 ages past the 3-day npmMinimalAgeGate
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [7.72.1]

### Fixed

- Fixed a bug where users in restricted regions could open perps positions from the Market Insights page (#28688)

## [7.72.0]

### Added
Expand Down Expand Up @@ -11156,7 +11162,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [#957](https://github.com/MetaMask/metamask-mobile/pull/957): fix timeouts (#957)
- [#954](https://github.com/MetaMask/metamask-mobile/pull/954): Bugfix: onboarding navigation (#954)

[Unreleased]: https://github.com/MetaMask/metamask-mobile/compare/v7.72.0...HEAD
[Unreleased]: https://github.com/MetaMask/metamask-mobile/compare/v7.72.1...HEAD
[7.72.1]: https://github.com/MetaMask/metamask-mobile/compare/v7.72.0...v7.72.1
[7.72.0]: https://github.com/MetaMask/metamask-mobile/compare/v7.71.1...v7.72.0
[7.71.1]: https://github.com/MetaMask/metamask-mobile/compare/v7.71.0...v7.71.1
[7.71.0]: https://github.com/MetaMask/metamask-mobile/compare/v7.70.1...v7.71.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const mockCreateEventBuilder = jest.fn(
const mockUseSwapBridgeNavigation = jest.fn((_options: unknown) => ({
goToSwaps: mockGoToSwaps,
}));
const mockPerpsTrack = jest.fn();
let mockIsEligible = true;

let mockRouteParams: {
assetSymbol: string;
Expand Down Expand Up @@ -228,6 +230,27 @@ jest.mock('../../../../hooks/useAnalytics/useAnalytics', () => ({
}),
}));

jest.mock('../../../Perps/selectors/perpsController', () => ({
selectPerpsEligibility: jest.fn(() => mockIsEligible),
}));

jest.mock('../../../../../selectors/accountsController', () => ({
...jest.requireActual('../../../../../selectors/accountsController'),
selectSelectedInternalAccountAddress: jest.fn(() => '0xMockAddress'),
}));

jest.mock('../../../Perps/components/PerpsBottomSheetTooltip', () => {
const { View: MockView } = jest.requireActual('react-native');
const Tooltip = (props: { testID?: string; onClose?: () => void }) => (
<MockView testID={props.testID ?? 'geo-block-tooltip'} />
);
return { __esModule: true, default: Tooltip };
});

jest.mock('../../../Perps/hooks/usePerpsEventTracking', () => ({
usePerpsEventTracking: () => ({ track: mockPerpsTrack }),
}));

jest.mock('@metamask/design-system-react-native', () => {
const actual = jest.requireActual('@metamask/design-system-react-native');
const { View } = jest.requireActual('react-native');
Expand Down Expand Up @@ -259,6 +282,7 @@ describe('MarketInsightsView', () => {
jest.spyOn(Linking, 'openURL').mockResolvedValue(undefined);
jest.clearAllMocks();
resetFeedbackCache();
mockIsEligible = true;
mockRouteParams = {
assetSymbol: 'ETH',
assetIdentifier: 'eip155:1/erc20:0x123',
Expand Down Expand Up @@ -729,7 +753,7 @@ describe('MarketInsightsView', () => {
expect(queryByTestId(MarketInsightsSelectorsIDs.BUY_BUTTON)).toBeNull();
});

it('navigates to PerpsOrderRedirect with long direction when Long button is pressed', () => {
it('navigates to PerpsOrderRedirect with long direction when Long button is pressed', async () => {
mockRouteParams = {
assetSymbol: 'ETH',
assetIdentifier: 'ETH',
Expand All @@ -751,7 +775,9 @@ describe('MarketInsightsView', () => {

const { getByTestId } = renderWithProvider(<MarketInsightsView />);

fireEvent.press(getByTestId(MarketInsightsSelectorsIDs.LONG_BUTTON));
await act(async () => {
fireEvent.press(getByTestId(MarketInsightsSelectorsIDs.LONG_BUTTON));
});

expect(mockNavigate).toHaveBeenCalledWith(
Routes.PERPS.ROOT,
Expand All @@ -763,7 +789,7 @@ describe('MarketInsightsView', () => {
expect(mockGoToSwaps).not.toHaveBeenCalled();
});

it('navigates to PerpsOrderRedirect with short direction when Short button is pressed', () => {
it('navigates to PerpsOrderRedirect with short direction when Short button is pressed', async () => {
mockRouteParams = {
assetSymbol: 'ETH',
assetIdentifier: 'ETH',
Expand All @@ -785,7 +811,9 @@ describe('MarketInsightsView', () => {

const { getByTestId } = renderWithProvider(<MarketInsightsView />);

fireEvent.press(getByTestId(MarketInsightsSelectorsIDs.SHORT_BUTTON));
await act(async () => {
fireEvent.press(getByTestId(MarketInsightsSelectorsIDs.SHORT_BUTTON));
});

expect(mockNavigate).toHaveBeenCalledWith(
Routes.PERPS.ROOT,
Expand All @@ -797,6 +825,47 @@ describe('MarketInsightsView', () => {
expect(mockGoToSwaps).not.toHaveBeenCalled();
});

it('shows geo-block modal instead of navigating when user is not eligible', async () => {
mockIsEligible = false;
mockRouteParams = {
assetSymbol: 'ETH',
assetIdentifier: 'ETH',
isPerps: true,
};
mockUseMarketInsights.mockReturnValue({
report: {
asset: 'eth',
generatedAt: '2026-02-17T11:55:00.000Z',
headline: 'ETH perps insight',
summary: 'Open interest rises',
trends: [],
sources: [],
},
isLoading: false,
error: null,
timeAgo: '1m ago',
});

const { getByTestId, queryByTestId } = renderWithProvider(
<MarketInsightsView />,
);

expect(
queryByTestId('market-insights-geo-block-tooltip'),
).not.toBeOnTheScreen();

await act(async () => {
fireEvent.press(getByTestId(MarketInsightsSelectorsIDs.LONG_BUTTON));
});

expect(getByTestId('market-insights-geo-block-tooltip')).toBeOnTheScreen();
expect(mockNavigate).not.toHaveBeenCalledWith(
Routes.PERPS.ROOT,
expect.anything(),
);
expect(mockPerpsTrack).toHaveBeenCalled();
});

it('navigates to swaps when swap button is pressed in token context', () => {
const { getByTestId, queryByTestId } = renderWithProvider(
<MarketInsightsView />,
Expand All @@ -814,7 +883,7 @@ describe('MarketInsightsView', () => {
);
});

it('sends perps_market analytics property (not caip19) in perps context', () => {
it('sends perps_market analytics property (not caip19) in perps context', async () => {
mockRouteParams = {
assetSymbol: 'ETH',
assetIdentifier: 'ETH',
Expand Down Expand Up @@ -864,7 +933,9 @@ describe('MarketInsightsView', () => {
);

// Long button carries perps_market, digest_id, and interaction_type 'long'
fireEvent.press(getByTestId(MarketInsightsSelectorsIDs.LONG_BUTTON));
await act(async () => {
fireEvent.press(getByTestId(MarketInsightsSelectorsIDs.LONG_BUTTON));
});
expect(mockTrackEvent).toHaveBeenCalledWith(
expect.objectContaining({
category: MetaMetricsEvents.MARKET_INSIGHTS_INTERACTION,
Expand All @@ -887,7 +958,9 @@ describe('MarketInsightsView', () => {
);

// Short button carries perps_market, digest_id, and interaction_type 'short'
fireEvent.press(getByTestId(MarketInsightsSelectorsIDs.SHORT_BUTTON));
await act(async () => {
fireEvent.press(getByTestId(MarketInsightsSelectorsIDs.SHORT_BUTTON));
});
expect(mockTrackEvent).toHaveBeenCalledWith(
expect.objectContaining({
category: MetaMetricsEvents.MARKET_INSIGHTS_INTERACTION,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
Pressable,
Animated,
Image,
Modal,
View,
useColorScheme,
} from 'react-native';
import Video from 'react-native-video';
Expand Down Expand Up @@ -83,6 +85,13 @@ import MarketInsightsFeedbackBottomSheet, {
import { useRampNavigation } from '../../../Ramp/hooks/useRampNavigation';
import parseRampIntent from '../../../Ramp/utils/parseRampIntent';
import { getDecimalChainId } from '../../../../../util/networks';
import { selectPerpsEligibility } from '../../../Perps/selectors/perpsController';
import PerpsBottomSheetTooltip from '../../../Perps/components/PerpsBottomSheetTooltip';
import {
PERPS_EVENT_PROPERTY,
PERPS_EVENT_VALUE,
} from '@metamask/perps-controller';
import { usePerpsEventTracking } from '../../../Perps/hooks/usePerpsEventTracking';

const feedbackByDigest = new Map<string, 'up' | 'down'>();

Expand Down Expand Up @@ -212,6 +221,11 @@ const MarketInsightsView: React.FC = () => {
[isDarkMode],
);

const isEligible = useSelector(selectPerpsEligibility);
const [isEligibilityModalVisible, setIsEligibilityModalVisible] =
useState(false);
const { track } = usePerpsEventTracking();

const { trackEvent, createEventBuilder } = useAnalytics();
const { toastRef } = useContext(ToastContext);
const theme = useAppThemeFromContext();
Expand Down Expand Up @@ -328,8 +342,23 @@ const MarketInsightsView: React.FC = () => {
assetSymbolProperty,
]);

const closeEligibilityModal = useCallback(() => {
setIsEligibilityModalVisible(false);
}, []);

const handlePerpsDirectionPress = useCallback(
(direction: 'long' | 'short') => {
async (direction: 'long' | 'short') => {
if (!isEligible) {
track(MetaMetricsEvents.PERPS_SCREEN_VIEWED, {
[PERPS_EVENT_PROPERTY.SCREEN_TYPE]:
PERPS_EVENT_VALUE.SCREEN_TYPE.GEO_BLOCK_NOTIF,
[PERPS_EVENT_PROPERTY.SOURCE]:
PERPS_EVENT_VALUE.SOURCE.MARKET_INSIGHTS,
});
setIsEligibilityModalVisible(true);
return;
}

const event = createEventBuilder(
MetaMetricsEvents.MARKET_INSIGHTS_INTERACTION,
)
Expand All @@ -347,6 +376,8 @@ const MarketInsightsView: React.FC = () => {
});
},
[
isEligible,
track,
navigation,
trackEvent,
createEventBuilder,
Expand Down Expand Up @@ -844,6 +875,20 @@ const MarketInsightsView: React.FC = () => {
onSubmit={handleFeedbackSubmit}
/>
) : null}

{isEligibilityModalVisible && (
// Android Compatibility: Wrap the <Modal> in a plain <View> component to prevent rendering issues and freezing.
<View>
<Modal visible transparent animationType="none" statusBarTranslucent>
<PerpsBottomSheetTooltip
isVisible
onClose={closeEligibilityModal}
contentKey="geo_block"
testID="market-insights-geo-block-tooltip"
/>
</Modal>
</View>
)}
</Box>
);
};
Expand Down
2 changes: 1 addition & 1 deletion app/constants/ota.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import otaConfig from '../../ota.config.js';
* Reset to v0 when releasing a new native build
* We keep this OTA_VERSION here to because changes in ota.config.js will affect the fingerprint and break the workflow in Github Actions
*/
export const OTA_VERSION: string = 'vX.XX.X';
export const OTA_VERSION: string = 'v7.72.1';
export const RUNTIME_VERSION = otaConfig.RUNTIME_VERSION;
export const PROJECT_ID = otaConfig.PROJECT_ID;
export const UPDATE_URL = otaConfig.UPDATE_URL;
1 change: 1 addition & 0 deletions app/controllers/perps/constants/eventNames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ export const PERPS_EVENT_VALUE = {
ADD_FUNDS_ACTION: 'add_funds_action',
CANCEL_ORDER: 'cancel_order',
ASSET_DETAIL_SCREEN: 'asset_detail_screen',
MARKET_INSIGHTS: 'market_insights',
// TAT-2449: Geo-block sources for close/modify actions
CLOSE_POSITION_ACTION: 'close_position_action',
MODIFY_POSITION_ACTION: 'modify_position_action',
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@
"@unrs/resolver-binding-wasm32-wasi": "npm:npm-empty-package@1.0.0",
"d3-color": "3.1.0",
"napi-postinstall": "npm:npm-empty-package@1.0.0",
"axios": "^1.13.5",
"axios": "^1.15.0",
"lodash": "4.18.1",
"redux-persist-filesystem-storage/react-native-blob-util": "^0.19.9",
"@ethersproject/providers/ws": "^7.5.10",
Expand Down Expand Up @@ -365,7 +365,7 @@
"@walletconnect/utils": "^2.23.0",
"@xmldom/xmldom": "^0.8.12",
"asyncstorage-down": "4.2.0",
"axios": "^1.13.5",
"axios": "^1.15.0",
"bignumber.js": "^9.0.1",
"bitcoin-address-validation": "2.2.3",
"bnjs4": "npm:bn.js@^4.12.3",
Expand Down
Loading
Loading