Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Transition from Multiple Networks with Same ChainID to Unique Networks with Distinct ChainIDs and Multiple RPC URLs #11705

Merged
merged 5 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,17 @@ exports[`CellSelectWithMenu should render with default settings correctly 1`] =
"alignItems": "center",
"backgroundColor": "#ffffff",
"flexDirection": "row",
"paddingRight": 20,
"width": "100%",
}
}
>
<TouchableOpacity
disabled={false}
style={
{
"flex": 1,
"opacity": 1,
"padding": 16,
"position": "relative",
"width": "90%",
"zIndex": 1,
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ListItemMultiSelectButtonProps } from './ListItemMultiSelectButton.type
// Defaults
export const DEFAULT_LISTITEMMULTISELECT_GAP = 16;
export const BUTTON_TEST_ID = 'button-menu-select-test-id';
export const BUTTON_TEXT_TEST_ID = 'button-text-select-test-id';

// Sample consts
export const SAMPLE_LISTITEMMULTISELECT_PROPS: ListItemMultiSelectButtonProps =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ const styleSheet = (params: {
return StyleSheet.create({
base: Object.assign(
{
flex: 1,
position: 'relative',
opacity: isDisabled ? 0.5 : 1,
padding: 16,
width: '90%',
zIndex: 1,
} as ViewStyle,
style,
Expand Down Expand Up @@ -71,10 +71,8 @@ const styleSheet = (params: {
backgroundColor: isSelected
? colors.primary.muted
: colors.background.default,
paddingRight: 20,
flexDirection: 'row',
alignItems: 'center',
width: '100%',
},
itemColumn: {
display: 'flex',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,17 @@ exports[`ListItemMultiSelectButton should render correctly with default props 1`
"alignItems": "center",
"backgroundColor": "#ffffff",
"flexDirection": "row",
"paddingRight": 20,
"width": "100%",
}
}
>
<TouchableOpacity
disabled={false}
style={
{
"flex": 1,
"opacity": 1,
"padding": 16,
"position": "relative",
"width": "90%",
"zIndex": 1,
}
}
Expand Down
1 change: 0 additions & 1 deletion app/components/Nav/App/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@
}
};

const DetectedTokensFlow = () => (

Check warning on line 567 in app/components/Nav/App/index.js

View workflow job for this annotation

GitHub Actions / scripts (lint)

Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (https://reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component “App” and pass data as props
<Stack.Navigator
mode={'modal'}
screenOptions={clearStackNavigatorOptions}
Expand All @@ -578,7 +578,7 @@
</Stack.Navigator>
);

const RootModalFlow = () => (

Check warning on line 581 in app/components/Nav/App/index.js

View workflow job for this annotation

GitHub Actions / scripts (lint)

Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (https://reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component “App” and pass data as props
<Stack.Navigator mode={'modal'} screenOptions={clearStackNavigatorOptions}>
<Stack.Screen
name={Routes.MODAL.WALLET_ACTIONS}
Expand Down Expand Up @@ -711,7 +711,6 @@
component={MultiRpcModal}
/>
) : null}

<Stack.Screen
name={Routes.SHEET.SHOW_TOKEN_ID}
component={ShowTokenIdSheet}
Expand All @@ -727,7 +726,7 @@
</Stack.Navigator>
);

const ImportPrivateKeyView = () => (

Check warning on line 729 in app/components/Nav/App/index.js

View workflow job for this annotation

GitHub Actions / scripts (lint)

Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (https://reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component “App” and pass data as props
<Stack.Navigator
screenOptions={{
headerShown: false,
Expand All @@ -748,7 +747,7 @@
</Stack.Navigator>
);

const ConnectQRHardwareFlow = () => (

Check warning on line 750 in app/components/Nav/App/index.js

View workflow job for this annotation

GitHub Actions / scripts (lint)

Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (https://reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component “App” and pass data as props
<Stack.Navigator
screenOptions={{
headerShown: false,
Expand All @@ -758,7 +757,7 @@
</Stack.Navigator>
);

const LedgerConnectFlow = () => (

Check warning on line 760 in app/components/Nav/App/index.js

View workflow job for this annotation

GitHub Actions / scripts (lint)

Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (https://reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component “App” and pass data as props
<Stack.Navigator
screenOptions={{
headerShown: false,
Expand All @@ -772,7 +771,7 @@
</Stack.Navigator>
);

const ConnectHardwareWalletFlow = () => (

Check warning on line 774 in app/components/Nav/App/index.js

View workflow job for this annotation

GitHub Actions / scripts (lint)

Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (https://reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component “App” and pass data as props
<Stack.Navigator name="ConnectHardwareWallet">
<Stack.Screen
name={Routes.HW.SELECT_DEVICE}
Expand All @@ -782,14 +781,14 @@
</Stack.Navigator>
);

const EditAccountNameFlow = () => (

Check warning on line 784 in app/components/Nav/App/index.js

View workflow job for this annotation

GitHub Actions / scripts (lint)

Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (https://reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component “App” and pass data as props
<Stack.Navigator>
<Stack.Screen name="EditAccountName" component={EditAccountName} />
</Stack.Navigator>
);

// eslint-disable-next-line react/prop-types
const AddNetworkFlow = ({ route }) => (

Check warning on line 791 in app/components/Nav/App/index.js

View workflow job for this annotation

GitHub Actions / scripts (lint)

Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (https://reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component “App” and pass data as props
<Stack.Navigator>
<Stack.Screen
name="AddNetwork"
Expand Down
1 change: 0 additions & 1 deletion app/components/Nav/Main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,6 @@ const Main = (props) => {
networkImageSource: networkImage,
});
}

previousNetworkConfigurations.current = networkConfigurations;
}, [networkConfigurations, networkName, networkImage, toastRef]);

Expand Down
23 changes: 9 additions & 14 deletions app/components/UI/AccountInfoCard/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
MOCK_ADDRESS_1,
} from '../../../util/test/accountsControllerTestUtils';
import { RootState } from '../../../reducers';
import { RpcEndpointType } from '@metamask/network-controller';
import { mockNetworkState } from '../../../util/test/network';

jest.mock('../../../core/Engine', () => ({
resetState: jest.fn(),
Expand Down Expand Up @@ -48,20 +50,13 @@ const mockInitialState: DeepPartial<RootState> = {
},
},
NetworkController: {
selectedNetworkClientId: 'sepolia',
networksMetadata: {},
networkConfigurations: {
sepolia: {
id: 'sepolia',
rpcUrl: 'http://localhost/v3/',
chainId: '0xaa36a7',
ticker: 'ETH',
nickname: 'sepolia',
rpcPrefs: {
blockExplorerUrl: 'https://etherscan.com',
},
},
},
...mockNetworkState({
chainId: '0xaa36a7',
id: 'mainnet',
nickname: 'Sepolia',
ticker: 'SepoliaETH',
type: RpcEndpointType.Infura,
}),
},
TokenBalancesController: {
contractBalances: {},
Expand Down
5 changes: 4 additions & 1 deletion app/components/UI/NetworkModal/NetworkAdded/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ const createStyles = (colors: any) =>
flexDirection: 'row',
paddingVertical: 16,
},
base: {
padding: 16,
},
button: {
flex: 1,
},
Expand Down Expand Up @@ -41,7 +44,7 @@ const NetworkAdded = (props: NetworkAddedProps) => {
const styles = createStyles(colors);

return (
<View>
<View style={styles.base}>
<Text centered bold black big>
{strings('networks.new_network')}
</Text>
Expand Down
178 changes: 145 additions & 33 deletions app/components/UI/NetworkModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ import { useMetrics } from '../../../components/hooks/useMetrics';
import { toHex } from '@metamask/controller-utils';
import { rpcIdentifierUtility } from '../../../components/hooks/useSafeChains';
import Logger from '../../../util/Logger';
import { selectNetworkConfigurations } from '../../../selectors/networkController';
import {
NetworkConfiguration,
RpcEndpointType,
AddNetworkFields,
} from '@metamask/network-controller';

export interface SafeChain {
chainId: string;
Expand Down Expand Up @@ -162,6 +168,10 @@ const NetworkModals = (props: NetworkProps) => {
selectUseSafeChainsListValidation,
);

const networkConfigurationByChainId = useSelector(
selectNetworkConfigurations,
);

const customNetworkInformation = {
chainId,
blockExplorerUrl,
Expand Down Expand Up @@ -189,52 +199,154 @@ const NetworkModals = (props: NetworkProps) => {
checkNetwork();
}, [checkNetwork]);

const closeModal = () => {
const closeModal = async () => {
const { NetworkController } = Engine.context;
const url = new URLPARSE(rpcUrl);
!isPrivateConnection(url.hostname) && url.set('protocol', 'https:');
NetworkController.upsertNetworkConfiguration(
{
rpcUrl: url.href,

const existingNetwork = networkConfigurationByChainId[chainId];
let networkClientId;

if (existingNetwork) {
const updatedNetwork = await NetworkController.updateNetwork(
existingNetwork.chainId,
existingNetwork,
existingNetwork.chainId === chainId
? {
replacementSelectedRpcEndpointIndex:
existingNetwork.defaultRpcEndpointIndex,
}
: undefined,
);

networkClientId =
updatedNetwork?.rpcEndpoints?.[updatedNetwork.defaultRpcEndpointIndex]
?.networkClientId;
} else {
const addedNetwork = await NetworkController.addNetwork({
chainId,
ticker,
nickname,
rpcPrefs: { blockExplorerUrl },
},
{
// Metrics-related properties required, but the metric event is a no-op
// TODO: Use events for controller metric events
referrer: 'ignored',
source: 'ignored',
},
);
blockExplorerUrls: [blockExplorerUrl],
defaultRpcEndpointIndex: 0,
defaultBlockExplorerUrlIndex: 0,
name: nickname,
nativeCurrency: ticker,
rpcEndpoints: [
{
url: rpcUrl,
name: nickname,
type: RpcEndpointType.Custom,
},
],
});

networkClientId =
addedNetwork?.rpcEndpoints?.[addedNetwork.defaultRpcEndpointIndex]
?.networkClientId;
}

if (networkClientId) {
await NetworkController.setActiveNetwork(networkClientId);
}

onClose();
};

const switchNetwork = () => {
const handleExistingNetwork = async (
existingNetwork: NetworkConfiguration,
networkId: string,
) => {
const { NetworkController } = Engine.context;
const updatedNetwork = await NetworkController.updateNetwork(
existingNetwork.chainId,
existingNetwork,
existingNetwork.chainId === networkId
? {
replacementSelectedRpcEndpointIndex:
existingNetwork.defaultRpcEndpointIndex,
}
: undefined,
);

const { networkClientId } =
updatedNetwork?.rpcEndpoints?.[updatedNetwork.defaultRpcEndpointIndex] ??
{};

await NetworkController.setActiveNetwork(networkClientId);
};

const handleNewNetwork = async (
networkId: `0x${string}`,
networkRpcUrl: string,
name: string,
nativeCurrency: string,
networkBlockExplorerUrl: string,
) => {
const { NetworkController } = Engine.context;
const networkConfig = {
chainId: networkId,
blockExplorerUrls: networkBlockExplorerUrl
? [networkBlockExplorerUrl]
: [],
defaultRpcEndpointIndex: 0,
defaultBlockExplorerUrlIndex: blockExplorerUrl ? 0 : undefined,
name,
nativeCurrency,
rpcEndpoints: [
{
url: networkRpcUrl,
name,
type: RpcEndpointType.Custom,
},
],
} as AddNetworkFields;

return NetworkController.addNetwork(networkConfig);
};

const handleNavigation = (
onSwitchNetwork: () => void,
networkSwitchPopToWallet: boolean,
) => {
if (onSwitchNetwork) {
onSwitchNetwork();
} else {
networkSwitchPopToWallet
? navigation.navigate('WalletView')
: navigation.goBack();
}
};

const switchNetwork = async () => {
const { NetworkController, CurrencyRateController } = Engine.context;
const url = new URLPARSE(rpcUrl);
const existingNetwork = networkConfigurationByChainId[chainId];

CurrencyRateController.updateExchangeRate(ticker);
!isPrivateConnection(url.hostname) && url.set('protocol', 'https:');
NetworkController.upsertNetworkConfiguration(
{
rpcUrl: url.href,

if (!isPrivateConnection(url.hostname)) {
url.set('protocol', 'https:');
}

if (existingNetwork) {
await handleExistingNetwork(existingNetwork, chainId);
} else {
const addedNetwork = await handleNewNetwork(
chainId,
ticker,
rpcUrl,
nickname,
rpcPrefs: { blockExplorerUrl },
},
{
setActive: true,
// Metrics-related properties required, but the metric event is a no-op
// TODO: Use events for controller metric events
referrer: 'ignored',
source: 'ignored',
},
);
closeModal();
ticker,
blockExplorerUrl,
);
const { networkClientId } =
addedNetwork?.rpcEndpoints?.[addedNetwork.defaultRpcEndpointIndex] ??
{};

NetworkController.setActiveNetwork(networkClientId);
}
onClose();

if (onNetworkSwitch) {
onNetworkSwitch();
handleNavigation(onNetworkSwitch, shouldNetworkSwitchPopToWallet);
} else {
shouldNetworkSwitchPopToWallet
? navigation.navigate('WalletView')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ function render(Component: React.ComponentType, chainId?: `0x${string}`) {
chainId: '0x89',
id: 'networkId1',
nickname: 'Polygon Mainnet',
ticker: 'MATIC',
ticker: 'POL',
},
),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,16 +154,22 @@ function NetworkSwitcher() {
const switchNetwork = useCallback(
(networkConfiguration) => {
const { CurrencyRateController, NetworkController } = Engine.context;
const entry = Object.entries(networkConfigurations).find(
([_a, { chainId }]) => chainId === networkConfiguration.chainId,
const config = Object.values(networkConfigurations).find(
({ chainId }) => chainId === networkConfiguration.chainId,
salimtb marked this conversation as resolved.
Show resolved Hide resolved
);

if (entry) {
const [networkConfigurationId] = entry;
const { ticker } = networkConfiguration;
if (config) {
const {
nativeCurrency: ticker,
rpcEndpoints,
defaultRpcEndpointIndex,
} = config;

const { networkClientId } =
rpcEndpoints?.[defaultRpcEndpointIndex] ?? {};

CurrencyRateController.updateExchangeRate(ticker);
NetworkController.setActiveNetwork(networkConfigurationId);
NetworkController.setActiveNetwork(networkClientId);
navigateToGetStarted();
}
},
Expand Down
Loading
Loading