Add functionality to configure EVM networks (#74)

* Configure EVM networks

* Display added EVM networks in network drop down

* Add network for configured networks
This commit is contained in:
shreerang6921 2024-04-02 19:37:40 +05:30 committed by Nabarun Gogoi
parent 0dea0082b4
commit 703ea72c1f
10 changed files with 255 additions and 19 deletions

View File

@ -31,6 +31,7 @@
"patch-package": "^8.0.0", "patch-package": "^8.0.0",
"postinstall-postinstall": "^2.1.0", "postinstall-postinstall": "^2.1.0",
"react": "18.2.0", "react": "18.2.0",
"react-hook-form": "^7.51.2",
"react-native": "0.73.3", "react-native": "0.73.3",
"react-native-config": "^1.5.1", "react-native-config": "^1.5.1",
"react-native-get-random-values": "^1.10.0", "react-native-get-random-values": "^1.10.0",

View File

@ -26,6 +26,7 @@ import { web3wallet } from './utils/wallet-connect/WalletConnectUtils';
import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Data'; import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Data';
import { getSignParamsMessage } from './utils/wallet-connect/Helpers'; import { getSignParamsMessage } from './utils/wallet-connect/Helpers';
import ApproveTransaction from './screens/ApproveTransaction'; import ApproveTransaction from './screens/ApproveTransaction';
import AddNetwork from './screens/AddNetwork';
const Stack = createNativeStackNavigator<StackParamsList>(); const Stack = createNativeStackNavigator<StackParamsList>();
@ -220,6 +221,13 @@ const App = (): React.JSX.Element => {
title: 'Approve transaction', title: 'Approve transaction',
}} }}
/> />
<Stack.Screen
name="AddNetwork"
component={AddNetwork}
options={{
title: 'Add Network',
}}
/>
</Stack.Navigator> </Stack.Navigator>
<PairingModal <PairingModal
visible={modalVisible} visible={modalVisible}

View File

@ -187,6 +187,19 @@ const Accounts = ({
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<View style={styles.signLink}>
<TouchableOpacity
onPress={() => {
navigation.navigate('AddNetwork');
}}>
<Text
variant="titleSmall"
style={[styles.hyperlink, { color: theme.colors.primary }]}>
Add Network
</Text>
</TouchableOpacity>
</View>
</View> </View>
</ScrollView> </ScrollView>
); );

View File

@ -1,14 +1,34 @@
import React, { useState } from 'react'; import React, { useMemo, useState } from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import { List } from 'react-native-paper'; import { List } from 'react-native-paper';
import { NetworkDropdownProps } from '../types'; import { NetworkDropdownProps } from '../types';
import styles from '../styles/stylesheet'; import styles from '../styles/stylesheet';
import { useAccounts } from '../context/AccountsContext';
const NetworkDropdown = ({ updateNetwork }: NetworkDropdownProps) => { const NetworkDropdown = ({ updateNetwork }: NetworkDropdownProps) => {
const [expanded, setExpanded] = useState<boolean>(false); const [expanded, setExpanded] = useState<boolean>(false);
const [selectedNetwork, setSelectedNetwork] = useState<string>('Ethereum'); const [selectedNetwork, setSelectedNetwork] = useState<string>('Ethereum');
const { networksData } = useAccounts();
const networks = useMemo(() => {
const defaultNetworks = [
{ value: 'eth', chainId: 'eip155:1', displayName: 'Ethereum' },
{ value: 'cosmos', chainId: 'cosmos:cosmoshub-4', displayName: 'Cosmos' },
];
networksData.forEach(network => {
defaultNetworks.push({
value: network.networkType,
chainId: network.chainId,
displayName: network.networkName,
});
});
return defaultNetworks;
}, [networksData]);
const handleNetworkPress = (network: string, displayName: string) => { const handleNetworkPress = (network: string, displayName: string) => {
updateNetwork(network); updateNetwork(network);
setSelectedNetwork(displayName); setSelectedNetwork(displayName);
@ -23,7 +43,7 @@ const NetworkDropdown = ({ updateNetwork }: NetworkDropdownProps) => {
onPress={() => setExpanded(!expanded)}> onPress={() => setExpanded(!expanded)}>
{networks.map(network => ( {networks.map(network => (
<List.Item <List.Item
key={network.value} key={network.chainId}
title={network.displayName} title={network.displayName}
onPress={() => onPress={() =>
handleNetworkPress(network.value, network.displayName) handleNetworkPress(network.value, network.displayName)
@ -35,9 +55,4 @@ const NetworkDropdown = ({ updateNetwork }: NetworkDropdownProps) => {
); );
}; };
const networks = [
{ value: 'eth', displayName: 'Ethereum' },
{ value: 'cosmos', displayName: 'Cosmos' },
];
export { NetworkDropdown }; export { NetworkDropdown };

View File

@ -1,17 +1,25 @@
import React, { createContext, useContext, useState } from 'react'; import React, { createContext, useContext, useState } from 'react';
import { AccountsState } from '../types'; import { AccountsState, NetworksDataState } from '../types';
const AccountsContext = createContext<{ const AccountsContext = createContext<{
accounts: AccountsState; accounts: AccountsState;
setAccounts: (account: AccountsState) => void; setAccounts: (account: AccountsState) => void;
currentIndex: number; currentIndex: number;
setCurrentIndex: (index: number) => void; setCurrentIndex: (index: number) => void;
networksData: NetworksDataState[];
setNetworksData: (networksDataArray: NetworksDataState[]) => void;
networkType: string;
setNetworkType: (networkType: string) => void;
}>({ }>({
accounts: { ethAccounts: [], cosmosAccounts: [] }, accounts: { ethAccounts: [], cosmosAccounts: [] },
setAccounts: () => {}, setAccounts: () => {},
currentIndex: 0, currentIndex: 0,
setCurrentIndex: () => {}, setCurrentIndex: () => {},
networksData: [],
setNetworksData: () => {},
networkType: '',
setNetworkType: () => {},
}); });
const useAccounts = () => { const useAccounts = () => {
@ -24,10 +32,21 @@ const AccountsProvider = ({ children }: { children: any }) => {
ethAccounts: [], ethAccounts: [],
cosmosAccounts: [], cosmosAccounts: [],
}); });
const [networksData, setNetworksData] = useState<NetworksDataState[]>([]);
const [currentIndex, setCurrentIndex] = useState<number>(0); const [currentIndex, setCurrentIndex] = useState<number>(0);
const [networkType, setNetworkType] = useState<string>('eth');
return ( return (
<AccountsContext.Provider <AccountsContext.Provider
value={{ accounts, setAccounts, currentIndex, setCurrentIndex }}> value={{
accounts,
setAccounts,
currentIndex,
setCurrentIndex,
networksData,
setNetworksData,
networkType,
setNetworkType,
}}>
{children} {children}
</AccountsContext.Provider> </AccountsContext.Provider>
); );

146
src/screens/AddNetwork.tsx Normal file
View File

@ -0,0 +1,146 @@
import React from 'react';
import { View } from 'react-native';
import { useForm, Controller } from 'react-hook-form';
import { TextInput, Button, HelperText } from 'react-native-paper';
import styles from '../styles/stylesheet';
import { NetworksDataState, StackParamsList } from '../types';
import { useAccounts } from '../context/AccountsContext';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useNavigation } from '@react-navigation/native';
// TODO: Add validation to form inputs
const AddNetwork = () => {
const navigation =
useNavigation<NativeStackNavigationProp<StackParamsList>>();
const {
control,
formState: { errors, isValid },
handleSubmit,
} = useForm<NetworksDataState>({
mode: 'onChange',
});
const { networksData, setNetworksData } = useAccounts();
const submit = (data: NetworksDataState) => {
setNetworksData([...networksData, data]);
navigation.navigate('Laconic');
};
return (
<View style={styles.signPage}>
<Controller
control={control}
defaultValue=""
name="networkName"
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
label="Network Name"
value={value}
onBlur={onBlur}
onChangeText={value => onChange(value)}
/>
<HelperText type="error">{errors.networkName?.message}</HelperText>
</>
)}
/>
<Controller
control={control}
name="rpcUrl"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
label="New RPC URL"
onBlur={onBlur}
value={value}
onChangeText={value => onChange(value)}
/>
<HelperText type="error">{errors.rpcUrl?.message}</HelperText>
</>
)}
/>
<Controller
control={control}
name="chainId"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Chain ID"
onBlur={onBlur}
onChangeText={value => onChange(value)}
/>
<HelperText type="error">{errors.chainId?.message}</HelperText>
</>
)}
/>
<Controller
control={control}
name="currencySymbol"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Currency Symbol"
onBlur={onBlur}
onChangeText={value => onChange(value)}
/>
</>
)}
/>
<HelperText type="error">{errors.currencySymbol?.message}</HelperText>
<Controller
control={control}
defaultValue=""
name="blockExplorerUrl"
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Block Explorer URL (Optional)"
onBlur={onBlur}
onChangeText={value => onChange(value)}
/>
</>
)}
/>
<HelperText type="error">{errors.blockExplorerUrl?.message}</HelperText>
<Controller
control={control}
// TODO: Use state to toggle between 'eth' and 'cosmos'
defaultValue="eth"
name="networkType"
render={({ field: { onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
disabled
label="Block Explorer URL (Optional)"
onBlur={onBlur}
/>
</>
)}
/>
<HelperText type="error">{errors.blockExplorerUrl?.message}</HelperText>
<Button
mode="contained"
onPress={handleSubmit(submit)}
disabled={!isValid}>
Submit
</Button>
</View>
);
};
export default AddNetwork;

View File

@ -25,11 +25,11 @@ import {
rejectWalletConnectRequest, rejectWalletConnectRequest,
} from '../utils/wallet-connect/WalletConnectRequests'; } from '../utils/wallet-connect/WalletConnectRequests';
import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils';
import { EIP155_CHAINS } from '../utils/wallet-connect/EIP155Data';
import DataBox from '../components/DataBox'; import DataBox from '../components/DataBox';
import { getPathKey } from '../utils/misc'; import { getPathKey } from '../utils/misc';
import { COSMOS_TESTNET_CHAINS } from '../utils/wallet-connect/COSMOSData'; import { COSMOS_TESTNET_CHAINS } from '../utils/wallet-connect/COSMOSData';
import { COSMOS_DENOM } from '../utils/constants'; import { COSMOS_DENOM } from '../utils/constants';
import { useAccounts } from '../context/AccountsContext';
type SignRequestProps = NativeStackScreenProps< type SignRequestProps = NativeStackScreenProps<
StackParamsList, StackParamsList,
@ -37,6 +37,8 @@ type SignRequestProps = NativeStackScreenProps<
>; >;
const ApproveTransaction = ({ route }: SignRequestProps) => { const ApproveTransaction = ({ route }: SignRequestProps) => {
const { networksData } = useAccounts();
const requestSession = route.params.requestSessionData; const requestSession = route.params.requestSessionData;
const requestName = requestSession.peer.metadata.name; const requestName = requestSession.peer.metadata.name;
const requestIcon = requestSession.peer.metadata.icons[0]; const requestIcon = requestSession.peer.metadata.icons[0];
@ -55,9 +57,17 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
const provider = useMemo(() => { const provider = useMemo(() => {
if (network === 'eth') { if (network === 'eth') {
return new providers.JsonRpcProvider(EIP155_CHAINS[chainId].rpc); const currentChain = networksData.find(
networkData => networkData.chainId === chainId,
);
if (!currentChain) {
throw new Error('Requested chain not supported');
}
return new providers.JsonRpcProvider(currentChain.rpcUrl);
} }
}, [chainId, network]); }, [chainId, network, networksData]);
const navigation = const navigation =
useNavigation<NativeStackNavigationProp<StackParamsList>>(); useNavigation<NativeStackNavigationProp<StackParamsList>>();

View File

@ -28,8 +28,14 @@ const WCLogo = () => {
}; };
const HomeScreen = () => { const HomeScreen = () => {
const { accounts, setAccounts, currentIndex, setCurrentIndex } = const {
useAccounts(); accounts,
setAccounts,
currentIndex,
setCurrentIndex,
networkType,
setNetworkType,
} = useAccounts();
const { setActiveSessions } = useWalletConnect(); const { setActiveSessions } = useWalletConnect();
const navigation = const navigation =
@ -54,7 +60,6 @@ const HomeScreen = () => {
const [isWalletCreating, setIsWalletCreating] = useState<boolean>(false); const [isWalletCreating, setIsWalletCreating] = useState<boolean>(false);
const [walletDialog, setWalletDialog] = useState<boolean>(false); const [walletDialog, setWalletDialog] = useState<boolean>(false);
const [resetWalletDialog, setResetWalletDialog] = useState<boolean>(false); const [resetWalletDialog, setResetWalletDialog] = useState<boolean>(false);
const [network, setNetwork] = useState<string>('eth');
const [isAccountsFetched, setIsAccountsFetched] = useState<boolean>(false); const [isAccountsFetched, setIsAccountsFetched] = useState<boolean>(false);
const [phrase, setPhrase] = useState(''); const [phrase, setPhrase] = useState('');
@ -95,11 +100,11 @@ const HomeScreen = () => {
setActiveSessions({}); setActiveSessions({});
hideResetDialog(); hideResetDialog();
setNetwork('eth'); setNetworkType('eth');
}; };
const updateNetwork = (newNetwork: string) => { const updateNetwork = (newNetwork: string) => {
setNetwork(newNetwork); setNetworkType(newNetwork);
setCurrentIndex(0); setCurrentIndex(0);
}; };
@ -140,12 +145,12 @@ const HomeScreen = () => {
) : isWalletCreated ? ( ) : isWalletCreated ? (
<> <>
<NetworkDropdown <NetworkDropdown
selectedNetwork={network} selectedNetwork={networkType}
updateNetwork={updateNetwork} updateNetwork={updateNetwork}
/> />
<View style={styles.accountComponent}> <View style={styles.accountComponent}>
<Accounts <Accounts
network={network} network={networkType}
currentIndex={currentIndex} currentIndex={currentIndex}
updateIndex={updateIndex} updateIndex={updateIndex}
/> />

View File

@ -22,6 +22,7 @@ export type StackParamsList = {
InvalidPath: undefined; InvalidPath: undefined;
WalletConnect: undefined; WalletConnect: undefined;
AddSession: undefined; AddSession: undefined;
AddNetwork: undefined;
}; };
export type Account = { export type Account = {
@ -46,6 +47,10 @@ export type AccountsProps = {
export type NetworkDropdownProps = { export type NetworkDropdownProps = {
selectedNetwork: string; selectedNetwork: string;
updateNetwork: (network: string) => void; updateNetwork: (network: string) => void;
customNetwork?: {
value: string;
displayName: string;
};
}; };
export type AccountsState = { export type AccountsState = {
@ -53,6 +58,15 @@ export type AccountsState = {
cosmosAccounts: Account[]; cosmosAccounts: Account[];
}; };
export type NetworksDataState = {
networkName: string;
rpcUrl: string;
chainId: string;
currencySymbol: string;
blockExplorerUrl?: string;
networkType: string;
};
export type SignMessageParams = { export type SignMessageParams = {
message: string; message: string;
network: string; network: string;

View File

@ -7803,6 +7803,11 @@ react-freeze@^1.0.0:
resolved "https://registry.yarnpkg.com/react-freeze/-/react-freeze-1.0.3.tgz#5e3ca90e682fed1d73a7cb50c2c7402b3e85618d" resolved "https://registry.yarnpkg.com/react-freeze/-/react-freeze-1.0.3.tgz#5e3ca90e682fed1d73a7cb50c2c7402b3e85618d"
integrity sha512-ZnXwLQnGzrDpHBHiC56TXFXvmolPeMjTn1UOm610M4EXGzbEDR7oOIyS2ZiItgbs6eZc4oU/a0hpk8PrcKvv5g== integrity sha512-ZnXwLQnGzrDpHBHiC56TXFXvmolPeMjTn1UOm610M4EXGzbEDR7oOIyS2ZiItgbs6eZc4oU/a0hpk8PrcKvv5g==
react-hook-form@^7.51.2:
version "7.51.2"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.51.2.tgz#79f7f72ee217c5114ff831012d1a7ec344096e7f"
integrity sha512-y++lwaWjtzDt/XNnyGDQy6goHskFualmDlf+jzEZvjvz6KWDf7EboL7pUvRCzPTJd0EOPpdekYaQLEvvG6m6HA==
"react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.0.0, react-is@^18.2.0: "react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.0.0, react-is@^18.2.0:
version "18.2.0" version "18.2.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"