From 94bd8b6480a5c75b5334cd7c30210bbc19c448b3 Mon Sep 17 00:00:00 2001 From: IshaVenikar <145848618+IshaVenikar@users.noreply.github.com> Date: Wed, 10 Apr 2024 15:15:51 +0530 Subject: [PATCH] Persist network data (#84) * Store new network data * Store default networks in keystore (#86) * Add default nws in keystore * Fix duplicate networks * Display correct currency symbols for eth and cosmos tx * Fix currency display * Use wei for eth --- index.js | 17 ++++--- src/components/Accounts.tsx | 4 +- src/components/NetworkDropdown.tsx | 4 +- src/components/PairingModal.tsx | 5 ++- src/context/AccountsContext.tsx | 37 +--------------- src/context/NetworksContext.tsx | 71 ++++++++++++++++++++++++++++++ src/screens/AddNetwork.tsx | 11 +++-- src/screens/ApproveTransaction.tsx | 24 +++++++--- src/screens/HomeScreen.tsx | 6 +-- src/screens/SignRequest.tsx | 5 ++- src/types.ts | 1 + src/utils/accounts.ts | 34 +++++++++++++- src/utils/constants.ts | 23 ++++++++++ 13 files changed, 178 insertions(+), 64 deletions(-) create mode 100644 src/context/NetworksContext.tsx create mode 100644 src/utils/constants.ts diff --git a/index.js b/index.js index 5a8a4aa..e160e8c 100644 --- a/index.js +++ b/index.js @@ -7,6 +7,7 @@ import { NavigationContainer } from '@react-navigation/native'; import App from './src/App'; import { AccountsProvider } from './src/context/AccountsContext'; +import { NetworksProvider } from './src/context/NetworksContext'; import { WalletConnectProvider } from './src/context/WalletConnectContext'; import { name as appName } from './app.json'; @@ -23,13 +24,15 @@ export default function Main() { }; return ( - - - - - - - + + + + + + + + + ); } diff --git a/src/components/Accounts.tsx b/src/components/Accounts.tsx index 13cb6fd..a55e639 100644 --- a/src/components/Accounts.tsx +++ b/src/components/Accounts.tsx @@ -15,6 +15,7 @@ import { useAccounts } from '../context/AccountsContext'; import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; import { EIP155_SIGNING_METHODS } from '../utils/wallet-connect/EIP155Data'; import { COSMOS_METHODS } from '../utils/wallet-connect/COSMOSData'; +import { useNetworks } from '../context/NetworksContext'; const Accounts = ({ network, @@ -24,7 +25,8 @@ const Accounts = ({ const navigation = useNavigation>(); - const { accounts, setAccounts, networksData, currentChainId } = useAccounts(); + const { accounts, setAccounts } = useAccounts(); + const { networksData, currentChainId } = useNetworks(); const [expanded, setExpanded] = useState(false); const [isAccountCreating, setIsAccountCreating] = useState(false); const [hdDialog, setHdDialog] = useState(false); diff --git a/src/components/NetworkDropdown.tsx b/src/components/NetworkDropdown.tsx index cc5d329..adabc58 100644 --- a/src/components/NetworkDropdown.tsx +++ b/src/components/NetworkDropdown.tsx @@ -4,10 +4,10 @@ import { List } from 'react-native-paper'; import { NetworkDropdownProps, NetworksDataState } from '../types'; import styles from '../styles/stylesheet'; -import { useAccounts } from '../context/AccountsContext'; +import { useNetworks } from '../context/NetworksContext'; const NetworkDropdown = ({ updateNetwork }: NetworkDropdownProps) => { - const { networksData } = useAccounts(); + const { networksData } = useNetworks(); const [expanded, setExpanded] = useState(false); const [selectedNetwork, setSelectedNetwork] = useState( diff --git a/src/components/PairingModal.tsx b/src/components/PairingModal.tsx index fdf849c..7781092 100644 --- a/src/components/PairingModal.tsx +++ b/src/components/PairingModal.tsx @@ -13,6 +13,7 @@ import { useAccounts } from '../context/AccountsContext'; import { useWalletConnect } from '../context/WalletConnectContext'; import { EIP155_SIGNING_METHODS } from '../utils/wallet-connect/EIP155Data'; import { COSMOS_METHODS } from '../utils/wallet-connect/COSMOSData'; +import { useNetworks } from '../context/NetworksContext'; const PairingModal = ({ visible, @@ -21,8 +22,8 @@ const PairingModal = ({ setModalVisible, setToastVisible, }: PairingModalProps) => { - const { accounts, networksData, currentChainId, currentIndex } = - useAccounts(); + const { accounts, currentIndex } = useAccounts(); + const { networksData, currentChainId } = useNetworks(); const [isLoading, setIsLoading] = useState(false); const dappName = currentProposal?.params?.proposer?.metadata.name; diff --git a/src/context/AccountsContext.tsx b/src/context/AccountsContext.tsx index b97e3a6..f0edd2c 100644 --- a/src/context/AccountsContext.tsx +++ b/src/context/AccountsContext.tsx @@ -1,31 +1,21 @@ import React, { createContext, useContext, useState } from 'react'; -import { AccountsState, NetworksDataState } from '../types'; -import { EIP155_CHAINS } from '../utils/wallet-connect/EIP155Data'; -import { COSMOS_TESTNET_CHAINS } from '../utils/wallet-connect/COSMOSData'; +import { AccountsState } 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; - currentChainId: string; - setCurrentChainId: (currentChainId: string) => void; }>({ accounts: { ethAccounts: [], cosmosAccounts: [] }, setAccounts: () => {}, currentIndex: 0, setCurrentIndex: () => {}, - networksData: [], - setNetworksData: () => {}, networkType: '', setNetworkType: () => {}, - currentChainId: '', - setCurrentChainId: () => {}, }); const useAccounts = () => { @@ -38,29 +28,8 @@ const AccountsProvider = ({ children }: { children: any }) => { ethAccounts: [], cosmosAccounts: [], }); - const [networksData, setNetworksData] = useState([ - { - chainId: 'eip155:1', - networkName: EIP155_CHAINS['eip155:1'].name, - networkType: 'eth', - rpcUrl: EIP155_CHAINS['eip155:1'].rpc, - currencySymbol: 'ETH', - }, - { - chainId: 'cosmos:theta-testnet-001', - networkName: COSMOS_TESTNET_CHAINS['cosmos:theta-testnet-001'].name, - networkType: 'cosmos', - rpcUrl: COSMOS_TESTNET_CHAINS['cosmos:theta-testnet-001'].rpc, - nativeDenom: 'uatom', - addressPrefix: 'cosmos', - coinType: '118', - }, - ]); const [currentIndex, setCurrentIndex] = useState(0); const [networkType, setNetworkType] = useState('eth'); - const [currentChainId, setCurrentChainId] = useState( - networksData[0].chainId, - ); return ( { setAccounts, currentIndex, setCurrentIndex, - networksData, - setNetworksData, networkType, setNetworkType, - currentChainId, - setCurrentChainId, }}> {children} diff --git a/src/context/NetworksContext.tsx b/src/context/NetworksContext.tsx new file mode 100644 index 0000000..79af79d --- /dev/null +++ b/src/context/NetworksContext.tsx @@ -0,0 +1,71 @@ +import React, { createContext, useContext, useEffect, useState } from 'react'; + +import { NetworksDataState } from '../types'; +import { retrieveNetworksData, storeNetworkData } from '../utils/accounts'; +import { DEFAULTNETWORKS } from '../utils/constants'; + +const NetworksContext = createContext<{ + currentIndex: number; + setCurrentIndex: (index: number) => void; + networksData: NetworksDataState[]; + setNetworksData: React.Dispatch>; + networkType: string; + setNetworkType: (networkType: string) => void; + currentChainId?: string; + setCurrentChainId: (currentChainId: string) => void; +}>({ + currentIndex: 0, + setCurrentIndex: () => {}, + networksData: [], + setNetworksData: () => {}, + networkType: '', + setNetworkType: () => {}, + currentChainId: undefined, + setCurrentChainId: () => {}, +}); + +const useNetworks = () => { + const networksContext = useContext(NetworksContext); + return networksContext; +}; + +const NetworksProvider = ({ children }: { children: any }) => { + const [networksData, setNetworksData] = useState([]); + const [currentIndex, setCurrentIndex] = useState(0); + const [networkType, setNetworkType] = useState('eth'); + const [currentChainId, setCurrentChainId] = useState(); + + useEffect(() => { + const fetchData = async () => { + const retrievedNetworks = await retrieveNetworksData(); + if (retrievedNetworks.length === 0) { + for (const defaultNetwork of DEFAULTNETWORKS) { + await storeNetworkData(defaultNetwork); + } + } + const retrievedNewNetworks = await retrieveNetworksData(); + setNetworksData(retrievedNewNetworks); + setCurrentChainId(retrievedNewNetworks[0].chainId); + }; + + fetchData(); + }, []); + + return ( + + {children} + + ); +}; + +export { useNetworks, NetworksProvider }; diff --git a/src/screens/AddNetwork.tsx b/src/screens/AddNetwork.tsx index 3121b72..fa413c6 100644 --- a/src/screens/AddNetwork.tsx +++ b/src/screens/AddNetwork.tsx @@ -8,8 +8,9 @@ import { useNavigation } from '@react-navigation/native'; import styles from '../styles/stylesheet'; import { NetworksDataState, StackParamsList } from '../types'; -import { useAccounts } from '../context/AccountsContext'; import { SelectNetworkType } from '../components/SelectNetworkType'; +import { storeNetworkData } from '../utils/accounts'; +import { useNetworks } from '../context/NetworksContext'; // TODO: Add validation to form inputs const AddNetwork = () => { @@ -24,7 +25,7 @@ const AddNetwork = () => { mode: 'onChange', }); - const { networksData, setNetworksData } = useAccounts(); + const { networksData, setNetworksData } = useNetworks(); const [networkType, setNetworkType] = useState('eth'); @@ -35,12 +36,14 @@ const AddNetwork = () => { const submit = useCallback( async (data: NetworksDataState) => { const namespace = networkType === 'eth' ? 'eip155:' : 'cosmos:'; - const updatedData = { + const newNetworkData = { ...data, chainId: `${namespace}${data.chainId}`, networkType, + isDefault: false, }; - setNetworksData([...networksData, updatedData]); + setNetworksData([...networksData, newNetworkData]); + await storeNetworkData(newNetworkData); navigation.navigate('Laconic'); }, diff --git a/src/screens/ApproveTransaction.tsx b/src/screens/ApproveTransaction.tsx index a080c19..ae20a9d 100644 --- a/src/screens/ApproveTransaction.tsx +++ b/src/screens/ApproveTransaction.tsx @@ -27,7 +27,7 @@ import { import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; import DataBox from '../components/DataBox'; import { getPathKey } from '../utils/misc'; -import { useAccounts } from '../context/AccountsContext'; +import { useNetworks } from '../context/NetworksContext'; type SignRequestProps = NativeStackScreenProps< StackParamsList, @@ -35,7 +35,7 @@ type SignRequestProps = NativeStackScreenProps< >; const ApproveTransaction = ({ route }: SignRequestProps) => { - const { networksData } = useAccounts(); + const { networksData } = useNetworks(); const requestSession = route.params.requestSessionData; const requestName = requestSession.peer.metadata.name; @@ -239,24 +239,36 @@ const ApproveTransaction = ({ route }: SignRequestProps) => { {transaction && ( {network === 'eth' && ( diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index 678ab5d..ccf4909 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -17,6 +17,7 @@ import { useAccounts } from '../context/AccountsContext'; import { useWalletConnect } from '../context/WalletConnectContext'; import { NetworksDataState, StackParamsList } from '../types'; import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; +import { useNetworks } from '../context/NetworksContext'; const WCLogo = () => { return ( @@ -35,10 +36,9 @@ const HomeScreen = () => { setCurrentIndex, networkType, setNetworkType, - networksData, - currentChainId, - setCurrentChainId, } = useAccounts(); + + const { networksData, currentChainId, setCurrentChainId } = useNetworks(); const { setActiveSessions } = useWalletConnect(); const navigation = diff --git a/src/screens/SignRequest.tsx b/src/screens/SignRequest.tsx index 5dd60be..5a7ccc0 100644 --- a/src/screens/SignRequest.tsx +++ b/src/screens/SignRequest.tsx @@ -21,12 +21,12 @@ import { } from '../utils/wallet-connect/WalletConnectRequests'; import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; import { EIP155_SIGNING_METHODS } from '../utils/wallet-connect/EIP155Data'; -import { useAccounts } from '../context/AccountsContext'; +import { useNetworks } from '../context/NetworksContext'; type SignRequestProps = NativeStackScreenProps; const SignRequest = ({ route }: SignRequestProps) => { - const { networksData } = useAccounts(); + const { networksData } = useNetworks(); const requestSession = route.params.requestSessionData; const requestName = requestSession?.peer?.metadata?.name; @@ -152,6 +152,7 @@ const SignRequest = ({ route }: SignRequestProps) => { } const response = await approveWalletConnectRequest( + networksData, requestEvent, account, network, diff --git a/src/types.ts b/src/types.ts index 2f17e30..b632815 100644 --- a/src/types.ts +++ b/src/types.ts @@ -63,6 +63,7 @@ export type NetworksDataState = { nativeDenom?: string; addressPrefix?: string; coinType?: string; + isDefault: boolean; }; export type SignMessageParams = { diff --git a/src/utils/accounts.ts b/src/utils/accounts.ts index 7bdf899..168aed9 100644 --- a/src/utils/accounts.ts +++ b/src/utils/accounts.ts @@ -16,7 +16,7 @@ import { Secp256k1HdWallet } from '@cosmjs/amino'; import { AccountData } from '@cosmjs/proto-signing'; import { stringToPath } from '@cosmjs/crypto'; -import { Account, WalletDetails } from '../types'; +import { Account, NetworksDataState, WalletDetails } from '../types'; import { getHDPath, getPathKey, @@ -124,6 +124,36 @@ const addAccountFromHDPath = async ( } }; +const storeNetworkData = async ( + networkData: NetworksDataState, +): Promise => { + const networks = await getInternetCredentials('networks'); + const retrievedNetworks = + networks && networks.password ? JSON.parse(networks.password) : []; + let networkId = 0; + if (retrievedNetworks.length > 0) { + networkId = retrievedNetworks[retrievedNetworks.length - 1].networkId + 1; + } + + const updatedNetworks = [ + ...retrievedNetworks, + { networkId: networkId, ...networkData }, + ]; + await setInternetCredentials( + 'networks', + '_', + JSON.stringify(updatedNetworks), + ); +}; + +const retrieveNetworksData = async (): Promise => { + const networks = await getInternetCredentials('networks'); + const retrievedNetworks: NetworksDataState[] = + networks && networks.password ? JSON.parse(networks.password) : []; + + return retrievedNetworks; +}; + export const retrieveAccountsForNetwork = async ( network: string, accountsIndices: string, @@ -318,6 +348,8 @@ export { createWallet, addAccount, addAccountFromHDPath, + storeNetworkData, + retrieveNetworksData, retrieveAccounts, retrieveSingleAccount, resetWallet, diff --git a/src/utils/constants.ts b/src/utils/constants.ts new file mode 100644 index 0000000..19e7f6f --- /dev/null +++ b/src/utils/constants.ts @@ -0,0 +1,23 @@ +import { COSMOS_TESTNET_CHAINS } from './wallet-connect/COSMOSData'; +import { EIP155_CHAINS } from './wallet-connect/EIP155Data'; + +export const DEFAULTNETWORKS = [ + { + chainId: 'eip155:1', + networkName: EIP155_CHAINS['eip155:1'].name, + networkType: 'eth', + rpcUrl: EIP155_CHAINS['eip155:1'].rpc, + currencySymbol: 'ETH', + isDefault: true, + }, + { + chainId: 'cosmos:theta-testnet-001', + networkName: COSMOS_TESTNET_CHAINS['cosmos:theta-testnet-001'].name, + networkType: 'cosmos', + rpcUrl: COSMOS_TESTNET_CHAINS['cosmos:theta-testnet-001'].rpc, + nativeDenom: 'uatom', + addressPrefix: 'cosmos', + coinType: '118', + isDefault: true, + }, +];