diff --git a/package.json b/package.json index efa41e9..17228df 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "patch-package": "^8.0.0", "postinstall-postinstall": "^2.1.0", "react": "18.2.0", + "react-hook-form": "^7.51.2", "react-native": "0.73.3", "react-native-config": "^1.5.1", "react-native-get-random-values": "^1.10.0", diff --git a/src/App.tsx b/src/App.tsx index d62e52f..cd26e38 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -26,6 +26,7 @@ import { web3wallet } from './utils/wallet-connect/WalletConnectUtils'; import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Data'; import { getSignParamsMessage } from './utils/wallet-connect/Helpers'; import ApproveTransaction from './screens/ApproveTransaction'; +import AddNetwork from './screens/AddNetwork'; const Stack = createNativeStackNavigator(); @@ -220,6 +221,13 @@ const App = (): React.JSX.Element => { title: 'Approve transaction', }} /> + + + + { + navigation.navigate('AddNetwork'); + }}> + + Add Network + + + ); diff --git a/src/components/NetworkDropdown.tsx b/src/components/NetworkDropdown.tsx index 320eaae..c6a76b8 100644 --- a/src/components/NetworkDropdown.tsx +++ b/src/components/NetworkDropdown.tsx @@ -1,14 +1,34 @@ -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { View } from 'react-native'; import { List } from 'react-native-paper'; import { NetworkDropdownProps } from '../types'; import styles from '../styles/stylesheet'; +import { useAccounts } from '../context/AccountsContext'; const NetworkDropdown = ({ updateNetwork }: NetworkDropdownProps) => { const [expanded, setExpanded] = useState(false); const [selectedNetwork, setSelectedNetwork] = useState('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) => { updateNetwork(network); setSelectedNetwork(displayName); @@ -23,7 +43,7 @@ const NetworkDropdown = ({ updateNetwork }: NetworkDropdownProps) => { onPress={() => setExpanded(!expanded)}> {networks.map(network => ( 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 }; diff --git a/src/context/AccountsContext.tsx b/src/context/AccountsContext.tsx index 8e3cf39..1acb946 100644 --- a/src/context/AccountsContext.tsx +++ b/src/context/AccountsContext.tsx @@ -1,17 +1,25 @@ import React, { createContext, useContext, useState } from 'react'; -import { AccountsState } from '../types'; +import { AccountsState, NetworksDataState } from '../types'; const AccountsContext = createContext<{ accounts: AccountsState; setAccounts: (account: AccountsState) => void; currentIndex: number; setCurrentIndex: (index: number) => void; + networksData: NetworksDataState[]; + setNetworksData: (networksDataArray: NetworksDataState[]) => void; + networkType: string; + setNetworkType: (networkType: string) => void; }>({ accounts: { ethAccounts: [], cosmosAccounts: [] }, setAccounts: () => {}, currentIndex: 0, setCurrentIndex: () => {}, + networksData: [], + setNetworksData: () => {}, + networkType: '', + setNetworkType: () => {}, }); const useAccounts = () => { @@ -24,10 +32,21 @@ const AccountsProvider = ({ children }: { children: any }) => { ethAccounts: [], cosmosAccounts: [], }); + const [networksData, setNetworksData] = useState([]); const [currentIndex, setCurrentIndex] = useState(0); + const [networkType, setNetworkType] = useState('eth'); return ( + value={{ + accounts, + setAccounts, + currentIndex, + setCurrentIndex, + networksData, + setNetworksData, + networkType, + setNetworkType, + }}> {children} ); diff --git a/src/screens/AddNetwork.tsx b/src/screens/AddNetwork.tsx new file mode 100644 index 0000000..56b41c3 --- /dev/null +++ b/src/screens/AddNetwork.tsx @@ -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>(); + + const { + control, + formState: { errors, isValid }, + handleSubmit, + } = useForm({ + mode: 'onChange', + }); + + const { networksData, setNetworksData } = useAccounts(); + + const submit = (data: NetworksDataState) => { + setNetworksData([...networksData, data]); + navigation.navigate('Laconic'); + }; + return ( + + ( + <> + onChange(value)} + /> + {errors.networkName?.message} + + )} + /> + ( + <> + onChange(value)} + /> + {errors.rpcUrl?.message} + + )} + /> + ( + <> + onChange(value)} + /> + {errors.chainId?.message} + + )} + /> + ( + <> + onChange(value)} + /> + + )} + /> + {errors.currencySymbol?.message} + ( + <> + onChange(value)} + /> + + )} + /> + {errors.blockExplorerUrl?.message} + ( + <> + + + )} + /> + {errors.blockExplorerUrl?.message} + + + ); +}; + +export default AddNetwork; diff --git a/src/screens/ApproveTransaction.tsx b/src/screens/ApproveTransaction.tsx index a828806..66f478f 100644 --- a/src/screens/ApproveTransaction.tsx +++ b/src/screens/ApproveTransaction.tsx @@ -25,11 +25,11 @@ import { rejectWalletConnectRequest, } from '../utils/wallet-connect/WalletConnectRequests'; import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; -import { EIP155_CHAINS } from '../utils/wallet-connect/EIP155Data'; import DataBox from '../components/DataBox'; import { getPathKey } from '../utils/misc'; import { COSMOS_TESTNET_CHAINS } from '../utils/wallet-connect/COSMOSData'; import { COSMOS_DENOM } from '../utils/constants'; +import { useAccounts } from '../context/AccountsContext'; type SignRequestProps = NativeStackScreenProps< StackParamsList, @@ -37,6 +37,8 @@ type SignRequestProps = NativeStackScreenProps< >; const ApproveTransaction = ({ route }: SignRequestProps) => { + const { networksData } = useAccounts(); + const requestSession = route.params.requestSessionData; const requestName = requestSession.peer.metadata.name; const requestIcon = requestSession.peer.metadata.icons[0]; @@ -55,9 +57,17 @@ const ApproveTransaction = ({ route }: SignRequestProps) => { const provider = useMemo(() => { 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 = useNavigation>(); diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index 324d9c1..1b81461 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -28,8 +28,14 @@ const WCLogo = () => { }; const HomeScreen = () => { - const { accounts, setAccounts, currentIndex, setCurrentIndex } = - useAccounts(); + const { + accounts, + setAccounts, + currentIndex, + setCurrentIndex, + networkType, + setNetworkType, + } = useAccounts(); const { setActiveSessions } = useWalletConnect(); const navigation = @@ -54,7 +60,6 @@ const HomeScreen = () => { const [isWalletCreating, setIsWalletCreating] = useState(false); const [walletDialog, setWalletDialog] = useState(false); const [resetWalletDialog, setResetWalletDialog] = useState(false); - const [network, setNetwork] = useState('eth'); const [isAccountsFetched, setIsAccountsFetched] = useState(false); const [phrase, setPhrase] = useState(''); @@ -95,11 +100,11 @@ const HomeScreen = () => { setActiveSessions({}); hideResetDialog(); - setNetwork('eth'); + setNetworkType('eth'); }; const updateNetwork = (newNetwork: string) => { - setNetwork(newNetwork); + setNetworkType(newNetwork); setCurrentIndex(0); }; @@ -140,12 +145,12 @@ const HomeScreen = () => { ) : isWalletCreated ? ( <> diff --git a/src/types.ts b/src/types.ts index ec1b2e3..87734ad 100644 --- a/src/types.ts +++ b/src/types.ts @@ -22,6 +22,7 @@ export type StackParamsList = { InvalidPath: undefined; WalletConnect: undefined; AddSession: undefined; + AddNetwork: undefined; }; export type Account = { @@ -46,6 +47,10 @@ export type AccountsProps = { export type NetworkDropdownProps = { selectedNetwork: string; updateNetwork: (network: string) => void; + customNetwork?: { + value: string; + displayName: string; + }; }; export type AccountsState = { @@ -53,6 +58,15 @@ export type AccountsState = { cosmosAccounts: Account[]; }; +export type NetworksDataState = { + networkName: string; + rpcUrl: string; + chainId: string; + currencySymbol: string; + blockExplorerUrl?: string; + networkType: string; +}; + export type SignMessageParams = { message: string; network: string; diff --git a/yarn.lock b/yarn.lock index 4f584f4..6ccbc45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7803,6 +7803,11 @@ react-freeze@^1.0.0: resolved "https://registry.yarnpkg.com/react-freeze/-/react-freeze-1.0.3.tgz#5e3ca90e682fed1d73a7cb50c2c7402b3e85618d" 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: version "18.2.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"