Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
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
6 changes: 3 additions & 3 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 @@ -363,9 +363,9 @@
"@walletconnect/core": "^2.23.0",
"@walletconnect/react-native-compat": "^2.23.0",
"@walletconnect/utils": "^2.23.0",
"@xmldom/xmldom": "^0.8.10",
"@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