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
70 changes: 70 additions & 0 deletions ui/pages/bridge/hooks/useEnableMissingNetwork.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { renderHookWithProvider } from '../../../../test/lib/render-helpers-navigate';
import { createBridgeMockStore } from '../../../../test/data/bridge/mock-bridge-store';
import * as ActionsModule from '../../../store/actions';
import { useEnableMissingNetwork } from './useEnableMissingNetwork';

describe('useEnableMissingNetwork', () => {
const arrange = () => {
const mockEnableAllPopularNetworks = jest.spyOn(
ActionsModule,
'setEnabledAllPopularNetworks',
);

return {
mockEnableAllPopularNetworks,
};
};

beforeEach(() => {
jest.clearAllMocks();
});

it('enables popular network when not already enabled', () => {
const mocks = arrange();
const hook = renderHookWithProvider(
() => useEnableMissingNetwork(),
createBridgeMockStore({
metamaskStateOverrides: {
enabledNetworkMap: {
eip155: {
'0xe708': true,
},
},
},
}),
);

// Act - enable 0x1
hook.result.current('0x1');

// Assert - Adds 0x1 to enabled networks
expect(mocks.mockEnableAllPopularNetworks).toHaveBeenCalledTimes(1);
});

it('does not enable popular network if already enabled', () => {
const mocks = arrange();
const hook = renderHookWithProvider(
() => useEnableMissingNetwork(),
createBridgeMockStore(),
);

// Act - enable 0x1 (already enabled)
hook.result.current('0x1');
expect(mocks.mockEnableAllPopularNetworks).not.toHaveBeenCalled();
});

it('does not enable non-popular network', () => {
const mocks = arrange();
const hook = renderHookWithProvider(
() => useEnableMissingNetwork(),
createBridgeMockStore({
metamaskStateOverrides: {
enabledNetworkMap: { '0x1': true },
},
}),
);

hook.result.current('0x1111'); // not popular network
expect(mocks.mockEnableAllPopularNetworks).not.toHaveBeenCalled();
});
});
52 changes: 52 additions & 0 deletions ui/pages/bridge/hooks/useEnableMissingNetwork.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { useCallback } from 'react';
import {
formatChainIdToCaip,
formatChainIdToHex,
isNonEvmChainId,
} from '@metamask/bridge-controller';
import { type CaipChainId, type Hex, parseCaipChainId } from '@metamask/utils';
import { useSelector, useDispatch } from 'react-redux';
import { getEnabledNetworksByNamespace } from '../../../selectors';
import { FEATURED_NETWORK_CHAIN_IDS } from '../../../../shared/constants/network';
import { setEnabledAllPopularNetworks } from '../../../store/actions';

/**
* Ensures that any missing network gets added to the NetworkEnabledMap (which handles network polling)
*
* @returns callback to enable a network config.
*/
export const useEnableMissingNetwork = () => {
const enabledNetworksByNamespace = useSelector(getEnabledNetworksByNamespace);
const dispatch = useDispatch();

const enableMissingNetwork = useCallback(
(chainId: Hex | CaipChainId) => {
if (isNonEvmChainId(chainId)) {
return;
}

const enabledNetworkKeys = Object.keys(enabledNetworksByNamespace ?? {});

const caipChainId = formatChainIdToCaip(chainId);
const { namespace } = parseCaipChainId(caipChainId);
const hexChainId = formatChainIdToHex(chainId);

if (namespace) {
const isPopularNetwork =
FEATURED_NETWORK_CHAIN_IDS.includes(hexChainId);

if (isPopularNetwork) {
const isNetworkEnabled = enabledNetworkKeys.includes(hexChainId);
if (!isNetworkEnabled) {
// Bridging between popular networks indicates we want the 'select all' enabled
// This way users can see their full bridging tx activity
dispatch(setEnabledAllPopularNetworks());
}
}
}
},
[dispatch, enabledNetworksByNamespace],
);

return enableMissingNetwork;
};
70 changes: 1 addition & 69 deletions ui/pages/bridge/prepare/prepare-bridge-page.test.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
import React from 'react';
import { act } from '@testing-library/react';
import * as reactRouterUtils from 'react-router-dom-v5-compat';
import * as ReactReduxModule from 'react-redux';
import { userEvent } from '@testing-library/user-event';
import { toEvmCaipChainId } from '@metamask/multichain-network-controller';
import { renderHook } from '@testing-library/react-hooks';
import { renderWithProvider } from '../../../../test/lib/render-helpers-navigate';
import configureStore from '../../../store/store';
import { createBridgeMockStore } from '../../../../test/data/bridge/mock-bridge-store';
import { CHAIN_IDS } from '../../../../shared/constants/network';
import { createTestProviderTools } from '../../../../test/stub/provider';
import * as SelectorsModule from '../../../selectors/multichain/networks';
import * as ActionsModule from '../../../store/actions';
import PrepareBridgePage, {
useEnableMissingNetwork,
} from './prepare-bridge-page';
import PrepareBridgePage from './prepare-bridge-page';

// Mock the bridge hooks
jest.mock('../hooks/useGasIncluded7702', () => ({
Expand Down Expand Up @@ -297,65 +291,3 @@ describe('PrepareBridgePage', () => {
expect(input).toHaveDisplayValue('2.131');
});
});

describe('useEnableMissingNetwork', () => {
const arrangeReactReduxMocks = () => {
jest
.spyOn(ReactReduxModule, 'useSelector')
.mockImplementation((selector) => selector({}));
jest.spyOn(ReactReduxModule, 'useDispatch').mockReturnValue(jest.fn());
};

const arrange = () => {
arrangeReactReduxMocks();

const mockGetEnabledNetworksByNamespace = jest
.spyOn(SelectorsModule, 'getEnabledNetworksByNamespace')
.mockReturnValue({
'0x1': true,
'0xe708': true,
});
const mockEnableAllPopularNetworks = jest.spyOn(
ActionsModule,
'setEnabledAllPopularNetworks',
);

return {
mockGetEnabledNetworksByNamespace,
mockEnableAllPopularNetworks,
};
};

beforeEach(() => {
jest.clearAllMocks();
});

it('enables popular network when not already enabled', () => {
const mocks = arrange();
mocks.mockGetEnabledNetworksByNamespace.mockReturnValue({ '0xe708': true }); // Missing 0x1.
const hook = renderHook(() => useEnableMissingNetwork());

// Act - enable 0x1
hook.result.current('0x1');

// Assert - Adds 0x1 to enabled networks
expect(mocks.mockEnableAllPopularNetworks).toHaveBeenCalledWith();
});

it('does not enable popular network if already enabled', () => {
const mocks = arrange();
const hook = renderHook(() => useEnableMissingNetwork());

// Act - enable 0x1 (already enabled)
hook.result.current('0x1');
expect(mocks.mockEnableAllPopularNetworks).not.toHaveBeenCalled();
});

it('does not enable non-popular network', () => {
const mocks = arrange();
const hook = renderHook(() => useEnableMissingNetwork());

hook.result.current('0x1111'); // not popular network
expect(mocks.mockEnableAllPopularNetworks).not.toHaveBeenCalled();
});
});
58 changes: 5 additions & 53 deletions ui/pages/bridge/prepare/prepare-bridge-page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { BigNumber } from 'bignumber.js';
import { useSelector, useDispatch } from 'react-redux';
import classnames from 'classnames';
Expand All @@ -23,7 +17,7 @@ import {
isCrossChain,
isBitcoinChainId,
} from '@metamask/bridge-controller';
import { Hex, parseCaipChainId } from '@metamask/utils';
import type { Hex } from '@metamask/utils';
import {
setFromToken,
setFromTokenInputValue,
Expand Down Expand Up @@ -92,11 +86,7 @@ import { isNetworkAdded } from '../../../ducks/bridge/utils';
import MascotBackgroundAnimation from '../../swaps/mascot-background-animation/mascot-background-animation';
import { Column } from '../layout';
import useRamps from '../../../hooks/ramps/useRamps/useRamps';
import {
getCurrentKeyring,
getEnabledNetworksByNamespace,
getTokenList,
} from '../../../selectors';
import { getCurrentKeyring, getTokenList } from '../../../selectors';
import { isHardwareKeyring } from '../../../helpers/utils/hardware';
import { SECOND } from '../../../../shared/constants/time';
import { getIntlLocale } from '../../../ducks/locale/locale';
Expand All @@ -105,7 +95,6 @@ import {
getMultichainNativeCurrency,
getMultichainProviderConfig,
} from '../../../selectors/multichain';
import { setEnabledAllPopularNetworks } from '../../../store/actions';
import { MultichainBridgeQuoteCard } from '../quotes/multichain-bridge-quote-card';
import { TokenFeatureType } from '../../../../shared/types/security-alerts-api';
import { useTokenAlerts } from '../../../hooks/bridge/useTokenAlerts';
Expand All @@ -116,7 +105,6 @@ import type { BridgeToken } from '../../../ducks/bridge/types';
import { toAssetId } from '../../../../shared/lib/asset-utils';
import { endTrace, TraceName } from '../../../../shared/lib/trace';
import {
FEATURED_NETWORK_CHAIN_IDS,
TOKEN_OCCURRENCES_MAP,
MINIMUM_TOKEN_OCCURRENCES,
type ChainId,
Expand All @@ -125,45 +113,11 @@ import { useBridgeQueryParams } from '../../../hooks/bridge/useBridgeQueryParams
import { useSmartSlippage } from '../../../hooks/bridge/useSmartSlippage';
import { useGasIncluded7702 } from '../hooks/useGasIncluded7702';
import { useIsSendBundleSupported } from '../hooks/useIsSendBundleSupported';
import { useEnableMissingNetwork } from '../hooks/useEnableMissingNetwork';
import { BridgeInputGroup } from './bridge-input-group';
import { PrepareBridgePageFooter } from './prepare-bridge-page-footer';
import { DestinationAccountPickerModal } from './components/destination-account-picker-modal';

/**
* Ensures that any missing network gets added to the NetworkEnabledMap (which handles network polling)
*
* @returns callback to enable a network config.
*/
export const useEnableMissingNetwork = () => {
const enabledNetworksByNamespace = useSelector(getEnabledNetworksByNamespace);
const dispatch = useDispatch();

const enableMissingNetwork = useCallback(
(chainId: Hex) => {
const enabledNetworkKeys = Object.keys(enabledNetworksByNamespace ?? {});

const caipChainId = formatChainIdToCaip(chainId);
const { namespace } = parseCaipChainId(caipChainId);

if (namespace) {
const isPopularNetwork = FEATURED_NETWORK_CHAIN_IDS.includes(chainId);

if (isPopularNetwork) {
const isNetworkEnabled = enabledNetworkKeys.includes(chainId);
if (!isNetworkEnabled) {
// Bridging between popular networks indicates we want the 'select all' enabled
// This way users can see their full bridging tx activity
dispatch(setEnabledAllPopularNetworks());
}
}
}
},
[dispatch, enabledNetworksByNamespace],
);

return enableMissingNetwork;
};

const PrepareBridgePage = ({
onOpenSettings,
}: {
Expand Down Expand Up @@ -557,9 +511,7 @@ const PrepareBridgePage = ({
network: fromChain,
networks: fromChains,
onNetworkChange: (networkConfig) => {
if (isNetworkAdded(networkConfig)) {
enableMissingNetwork(networkConfig.chainId);
}
enableMissingNetwork(networkConfig.chainId);
dispatch(
setFromChain({
networkConfig,
Expand Down
Loading