From 3809ce88b1eaff91470d9f27631092823d719e4d Mon Sep 17 00:00:00 2001 From: shreerang6921 <68148922+shreerang6921@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:55:20 +0530 Subject: [PATCH] Update keystore data structure (#88) * Update keystore data structure (#83) * Update create wallet and retrieve accounts functionality for updated data structure * Refactor accounts state * Use constant variable for cosmos * Update add accounts incrementally and with custom HD path for updated data structure (#85) * Change data structure * Reset wallet change * Fix signEthMessage * Fix sign request * Fix pairing with laconic pay dApp and sending tokens * Add accounts to configured networks * Update add account from hd path flow * Handle review changes --------- Co-authored-by: Shreerang Kale * Remove network type state * Refactor create wallet code (#89) * Refactor create wallet code * Create cosmos accounts with correct address prefix * Use networks data from state while creating wallet * Refactor code and add network id in types (#91) * Refactor add new networks component * Add selected network state in context * Remove returning account from create wallet --------- Co-authored-by: IshaVenikar <145848618+IshaVenikar@users.noreply.github.com> --- src/App.tsx | 14 +- src/components/Accounts.tsx | 87 ++---- src/components/HDPath.tsx | 8 +- src/components/HDPathDialog.tsx | 5 + src/components/PairingModal.tsx | 51 ++-- src/components/SelectNetworkType.tsx | 3 +- src/context/AccountsContext.tsx | 20 +- src/context/NetworksContext.tsx | 24 +- src/screens/AddNetwork.tsx | 117 ++++++-- src/screens/ApproveTransaction.tsx | 110 +++---- src/screens/HomeScreen.tsx | 84 ++---- src/screens/SignMessage.tsx | 10 +- src/screens/SignRequest.tsx | 60 ++-- src/types.ts | 34 +-- src/utils/accounts.ts | 280 +++++++++--------- src/utils/constants.ts | 13 +- src/utils/misc.ts | 122 ++++---- src/utils/sign-message.ts | 24 +- .../wallet-connect/WalletConnectRequests.ts | 29 +- 19 files changed, 552 insertions(+), 543 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index cd26e38..321e454 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -27,6 +27,7 @@ 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'; +import { COSMOS, EIP155 } from './utils/constants'; const Stack = createNativeStackNavigator(); @@ -44,7 +45,7 @@ const App = (): React.JSX.Element => { const onSessionProposal = useCallback( async (proposal: SignClientTypes.EventArguments['session_proposal']) => { - if (!accounts.ethAccounts.length || !accounts.cosmosAccounts.length) { + if (!accounts.length || !accounts.length) { const { id } = proposal; await web3wallet!.rejectSession({ id, @@ -55,7 +56,7 @@ const App = (): React.JSX.Element => { setModalVisible(true); setCurrentProposal(proposal); }, - [accounts.ethAccounts, accounts.cosmosAccounts], + [accounts], ); const onSessionRequest = useCallback( @@ -68,7 +69,6 @@ const App = (): React.JSX.Element => { switch (request.method) { case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION: navigation.navigate('ApproveTransaction', { - network: 'eth', transaction: request.params[0], requestEvent, requestSessionData, @@ -77,7 +77,7 @@ const App = (): React.JSX.Element => { case EIP155_SIGNING_METHODS.PERSONAL_SIGN: navigation.navigate('SignRequest', { - network: 'eth', + namespace: EIP155, address: request.params[1], message: getSignParamsMessage(request.params), requestEvent, @@ -103,7 +103,7 @@ const App = (): React.JSX.Element => { ), }; navigation.navigate('SignRequest', { - network: 'cosmos', + namespace: COSMOS, address: request.params.signerAddress, message: JSON.stringify(message, undefined, 2), requestEvent, @@ -113,7 +113,7 @@ const App = (): React.JSX.Element => { break; case 'cosmos_signAmino': navigation.navigate('SignRequest', { - network: 'cosmos', + namespace: COSMOS, address: request.params.signerAddress, message: request.params.signDoc.memo, requestEvent, @@ -124,7 +124,6 @@ const App = (): React.JSX.Element => { case 'cosmos_sendTokens': navigation.navigate('ApproveTransaction', { - network: 'cosmos', transaction: request.params[0], requestEvent, requestSessionData, @@ -173,7 +172,6 @@ const App = (): React.JSX.Element => { options={{ headerTitle: () => Sign Message, }} - initialParams={{ selectedNetwork: 'Ethereum' }} /> { +const Accounts = ({ currentIndex, updateIndex }: AccountsProps) => { const navigation = useNavigation>(); const { accounts, setAccounts } = useAccounts(); - const { networksData, currentChainId } = useNetworks(); + const { selectedNetwork } = useNetworks(); const [expanded, setExpanded] = useState(false); const [isAccountCreating, setIsAccountCreating] = useState(false); const [hdDialog, setHdDialog] = useState(false); @@ -36,22 +33,7 @@ const Accounts = ({ const handlePress = () => setExpanded(!expanded); const updateAccounts = (account: Account) => { - switch (network) { - case 'eth': - setAccounts({ - ...accounts, - ethAccounts: [...accounts.ethAccounts, account], - }); - break; - case 'cosmos': - setAccounts({ - ...accounts, - cosmosAccounts: [...accounts.cosmosAccounts, account], - }); - break; - default: - console.error('Select a valid network!'); - } + setAccounts([...accounts, account]); }; useEffect(() => { @@ -70,24 +52,22 @@ const Accounts = ({ : undefined, ); - const currentNetwork = networksData.find( - networkData => networkData.chainId === currentChainId, - ); + const namespaceChainId = `${selectedNetwork?.namespace}:${selectedNetwork?.chainId}`; let updatedNamespaces; - switch (currentNetwork?.networkType) { - case 'eth': + switch (selectedNetwork?.namespace) { + case EIP155: updatedNamespaces = { eip155: { - chains: [currentNetwork.chainId], + chains: [namespaceChainId], // TODO: Debug optional namespace methods and events being required for approval methods: [ ...Object.values(EIP155_SIGNING_METHODS), ...(combinedNamespaces.eip155?.methods ?? []), ], events: [...(combinedNamespaces.eip155?.events ?? [])], - accounts: accounts.ethAccounts.map(ethAccount => { - return `${currentChainId}:${ethAccount.address}`; + accounts: accounts.map(ethAccount => { + return `${namespaceChainId}:${ethAccount.address}`; }), }, cosmos: { @@ -98,17 +78,17 @@ const Accounts = ({ }, }; break; - case 'cosmos': + case COSMOS: updatedNamespaces = { cosmos: { - chains: [currentNetwork.chainId], + chains: [namespaceChainId], methods: [ ...Object.values(COSMOS_METHODS), ...(combinedNamespaces.cosmos?.methods ?? []), ], events: [...(combinedNamespaces.cosmos?.events ?? [])], - accounts: accounts.cosmosAccounts.map(cosmosAccount => { - return `${currentChainId}:${cosmosAccount.address}`; + accounts: accounts.map(cosmosAccount => { + return `${namespaceChainId}:${cosmosAccount.address}`; }), }, eip155: { @@ -140,32 +120,21 @@ const Accounts = ({ const addAccountHandler = async () => { setIsAccountCreating(true); - const newAccount = await addAccount(network); + const newAccount = await addAccount(selectedNetwork!); setIsAccountCreating(false); if (newAccount) { updateAccounts(newAccount); - updateId(newAccount.counterId); + updateIndex(newAccount.index); } }; - const selectedAccounts: Account[] = (() => { - switch (network) { - case 'eth': - return accounts.ethAccounts; - case 'cosmos': - return accounts.cosmosAccounts; - default: - return []; - } - })(); - const renderAccountItems = () => - selectedAccounts.map(account => ( + accounts.map(account => ( { - updateId(account.counterId); + updateIndex(account.index); setExpanded(false); }} /> @@ -178,7 +147,7 @@ const Accounts = ({ visible={hdDialog} hideDialog={() => setHdDialog(false)} updateAccounts={updateAccounts} - updateIndex={updateId} + updateIndex={updateIndex} pathCode={pathCode} /> { setHdDialog(true); - setPathCode(network === 'eth' ? "m/44'/60'/" : "m/44'/118'/"); + // TODO: Use coin type while adding from HD path + setPathCode( + selectedNetwork!.namespace === EIP155 + ? "m/44'/60'/" + : "m/44'/118'/", + ); }}> Add Account from HD path - + { navigation.navigate('SignMessage', { - selectedNetwork: network, - accountInfo: selectedAccounts[currentIndex], + selectedNamespace: selectedNetwork!.namespace, + selectedChainId: selectedNetwork!.chainId, + accountInfo: accounts[currentIndex], }); }}> void; updateAccounts: (account: Account) => void; hideDialog: () => void; + selectedNetwork: NetworksDataState; }) => { const [isAccountCreating, setIsAccountCreating] = useState(false); const [path, setPath] = useState({ @@ -41,10 +43,10 @@ const HDPath = ({ pathCode + `${path.firstNumber}'/${path.secondNumber}/${path.thirdNumber}`; try { - const newAccount = await addAccountFromHDPath(hdPath); + const newAccount = await addAccountFromHDPath(hdPath, selectedNetwork); if (newAccount) { updateAccounts(newAccount); - updateIndex(newAccount.counterId); + updateIndex(newAccount.index); hideDialog(); } } catch (error) { diff --git a/src/components/HDPathDialog.tsx b/src/components/HDPathDialog.tsx index 918d6ba..6a8b168 100644 --- a/src/components/HDPathDialog.tsx +++ b/src/components/HDPathDialog.tsx @@ -1,7 +1,9 @@ import React from 'react'; import { Portal, Dialog } from 'react-native-paper'; + import { HDPathDialogProps } from '../types'; import HDPath from './HDPath'; +import { useNetworks } from '../context/NetworksContext'; const HDPathDialog = ({ visible, @@ -10,12 +12,15 @@ const HDPathDialog = ({ updateAccounts, pathCode, }: HDPathDialogProps) => { + const { selectedNetwork } = useNetworks(); + return ( Add account from HD path { const { accounts, currentIndex } = useAccounts(); - const { networksData, currentChainId } = useNetworks(); + const { selectedNetwork } = useNetworks(); const [isLoading, setIsLoading] = useState(false); const dappName = currentProposal?.params?.proposer?.metadata.name; @@ -83,37 +84,21 @@ const PairingModal = ({ } // Set selected account as the first account in supported namespaces - const sortedAccounts = Object.entries(accounts).reduce( - (acc: AccountsState, [key, value]) => { - let newValue = [...value]; + const sortedAccounts = [ + accounts[currentIndex], + ...accounts.filter((account, index) => index !== currentIndex), + ]; - // TODO: Implement selectedAccount instead of currentIndex in AccountsContext - if (value.length > currentIndex) { - const currentAccount = newValue[currentIndex]; - const remainingAccounts = newValue.filter( - (_, index) => index !== currentIndex, - ); - newValue = [currentAccount, ...remainingAccounts]; - } - - acc[key as 'ethAccounts' | 'cosmosAccounts'] = newValue; - return acc; - }, - { ethAccounts: [], cosmosAccounts: [] }, - ); - - const currentNetwork = networksData.find( - networkData => networkData.chainId === currentChainId, - ); + const namespaceChainId = `${selectedNetwork?.namespace}:${selectedNetwork?.chainId}`; const { optionalNamespaces, requiredNamespaces } = currentProposal.params; // TODO: Check with other dApps - switch (currentNetwork?.networkType) { - case 'eth': + switch (selectedNetwork?.namespace) { + case EIP155: return { eip155: { - chains: [currentNetwork.chainId], + chains: [namespaceChainId], // TODO: Debug optional namespace methods and events being required for approval methods: [ ...Object.values(EIP155_SIGNING_METHODS), @@ -124,8 +109,8 @@ const PairingModal = ({ ...(optionalNamespaces.eip155?.events ?? []), ...(requiredNamespaces.eip155?.events ?? []), ], - accounts: sortedAccounts.ethAccounts.map(ethAccount => { - return `${currentChainId}:${ethAccount.address}`; + accounts: sortedAccounts.map(ethAccount => { + return `${namespaceChainId}:${ethAccount.address}`; }), }, cosmos: { @@ -135,10 +120,10 @@ const PairingModal = ({ accounts: [], }, }; - case 'cosmos': + case COSMOS: return { cosmos: { - chains: [currentNetwork.chainId], + chains: [namespaceChainId], methods: [ ...Object.values(COSMOS_METHODS), ...(optionalNamespaces.cosmos?.methods ?? []), @@ -148,8 +133,8 @@ const PairingModal = ({ ...(optionalNamespaces.cosmos?.events ?? []), ...(requiredNamespaces.cosmos?.events ?? []), ], - accounts: sortedAccounts.cosmosAccounts.map(cosmosAccount => { - return `${currentChainId}:${cosmosAccount.address}`; + accounts: sortedAccounts.map(cosmosAccount => { + return `${namespaceChainId}:${cosmosAccount.address}`; }), }, eip155: { @@ -162,7 +147,7 @@ const PairingModal = ({ default: break; } - }, [accounts, currentProposal, networksData, currentChainId, currentIndex]); + }, [accounts, currentProposal, currentIndex, selectedNetwork]); const namespaces = useMemo(() => { return ( diff --git a/src/components/SelectNetworkType.tsx b/src/components/SelectNetworkType.tsx index cb05253..32234c7 100644 --- a/src/components/SelectNetworkType.tsx +++ b/src/components/SelectNetworkType.tsx @@ -3,6 +3,7 @@ import { View } from 'react-native'; import { Text, List } from 'react-native-paper'; import styles from '../styles/stylesheet'; +import { COSMOS, EIP155 } from '../utils/constants'; const SelectNetworkType = ({ updateNetworkType, @@ -16,7 +17,7 @@ const SelectNetworkType = ({ const handleNetworkPress = (network: string) => { setSelectedNetwork(network); - updateNetworkType(network.toLowerCase()); + updateNetworkType(network === 'ETH' ? EIP155 : COSMOS); setExpanded(false); }; diff --git a/src/context/AccountsContext.tsx b/src/context/AccountsContext.tsx index f0edd2c..2d5fe5e 100644 --- a/src/context/AccountsContext.tsx +++ b/src/context/AccountsContext.tsx @@ -1,21 +1,17 @@ import React, { createContext, useContext, useState } from 'react'; -import { AccountsState } from '../types'; +import { Account } from '../types'; const AccountsContext = createContext<{ - accounts: AccountsState; - setAccounts: (account: AccountsState) => void; + accounts: Account[]; + setAccounts: (account: Account[]) => void; currentIndex: number; setCurrentIndex: (index: number) => void; - networkType: string; - setNetworkType: (networkType: string) => void; }>({ - accounts: { ethAccounts: [], cosmosAccounts: [] }, + accounts: [], setAccounts: () => {}, currentIndex: 0, setCurrentIndex: () => {}, - networkType: '', - setNetworkType: () => {}, }); const useAccounts = () => { @@ -24,12 +20,8 @@ const useAccounts = () => { }; const AccountsProvider = ({ children }: { children: any }) => { - const [accounts, setAccounts] = useState({ - ethAccounts: [], - cosmosAccounts: [], - }); + const [accounts, setAccounts] = useState([]); const [currentIndex, setCurrentIndex] = useState(0); - const [networkType, setNetworkType] = useState('eth'); return ( { setAccounts, currentIndex, setCurrentIndex, - networkType, - setNetworkType, }}> {children} diff --git a/src/context/NetworksContext.tsx b/src/context/NetworksContext.tsx index 79af79d..ceb60ca 100644 --- a/src/context/NetworksContext.tsx +++ b/src/context/NetworksContext.tsx @@ -2,7 +2,7 @@ import React, { createContext, useContext, useEffect, useState } from 'react'; import { NetworksDataState } from '../types'; import { retrieveNetworksData, storeNetworkData } from '../utils/accounts'; -import { DEFAULTNETWORKS } from '../utils/constants'; +import { DEFAULT_NETWORKS, EIP155 } from '../utils/constants'; const NetworksContext = createContext<{ currentIndex: number; @@ -11,8 +11,10 @@ const NetworksContext = createContext<{ setNetworksData: React.Dispatch>; networkType: string; setNetworkType: (networkType: string) => void; - currentChainId?: string; - setCurrentChainId: (currentChainId: string) => void; + selectedNetwork?: NetworksDataState; + setSelectedNetwork: React.Dispatch< + React.SetStateAction + >; }>({ currentIndex: 0, setCurrentIndex: () => {}, @@ -20,8 +22,8 @@ const NetworksContext = createContext<{ setNetworksData: () => {}, networkType: '', setNetworkType: () => {}, - currentChainId: undefined, - setCurrentChainId: () => {}, + selectedNetwork: {} as NetworksDataState, + setSelectedNetwork: () => {}, }); const useNetworks = () => { @@ -32,20 +34,20 @@ const useNetworks = () => { const NetworksProvider = ({ children }: { children: any }) => { const [networksData, setNetworksData] = useState([]); const [currentIndex, setCurrentIndex] = useState(0); - const [networkType, setNetworkType] = useState('eth'); - const [currentChainId, setCurrentChainId] = useState(); + const [networkType, setNetworkType] = useState(EIP155); + const [selectedNetwork, setSelectedNetwork] = useState(); useEffect(() => { const fetchData = async () => { const retrievedNetworks = await retrieveNetworksData(); if (retrievedNetworks.length === 0) { - for (const defaultNetwork of DEFAULTNETWORKS) { + for (const defaultNetwork of DEFAULT_NETWORKS) { await storeNetworkData(defaultNetwork); } } const retrievedNewNetworks = await retrieveNetworksData(); setNetworksData(retrievedNewNetworks); - setCurrentChainId(retrievedNewNetworks[0].chainId); + setSelectedNetwork(retrievedNetworks[0]); }; fetchData(); @@ -60,8 +62,8 @@ const NetworksProvider = ({ children }: { children: any }) => { setNetworksData, networkType, setNetworkType, - currentChainId, - setCurrentChainId, + selectedNetwork, + setSelectedNetwork, }}> {children} diff --git a/src/screens/AddNetwork.tsx b/src/screens/AddNetwork.tsx index fa413c6..09fdbd4 100644 --- a/src/screens/AddNetwork.tsx +++ b/src/screens/AddNetwork.tsx @@ -2,15 +2,22 @@ import React, { useCallback, useState } from 'react'; import { ScrollView } from 'react-native'; import { useForm, Controller } from 'react-hook-form'; import { TextInput, Button, HelperText } from 'react-native-paper'; +import { + getInternetCredentials, + setInternetCredentials, +} from 'react-native-keychain'; +import { HDNode } from 'ethers/lib/utils'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { useNavigation } from '@react-navigation/native'; import styles from '../styles/stylesheet'; -import { NetworksDataState, StackParamsList } from '../types'; +import { NetworksDataState, NetworksFormData, StackParamsList } from '../types'; import { SelectNetworkType } from '../components/SelectNetworkType'; import { storeNetworkData } from '../utils/accounts'; import { useNetworks } from '../context/NetworksContext'; +import { COSMOS, EIP155 } from '../utils/constants'; +import { getCosmosAccounts } from '../utils/accounts'; // TODO: Add validation to form inputs const AddNetwork = () => { @@ -25,29 +32,79 @@ const AddNetwork = () => { mode: 'onChange', }); - const { networksData, setNetworksData } = useNetworks(); + const { setNetworksData } = useNetworks(); - const [networkType, setNetworkType] = useState('eth'); + const [namespace, setNamespace] = useState(EIP155); const updateNetworkType = (newNetworkType: string) => { - setNetworkType(newNetworkType); + setNamespace(newNetworkType); }; const submit = useCallback( - async (data: NetworksDataState) => { - const namespace = networkType === 'eth' ? 'eip155:' : 'cosmos:'; + async (data: NetworksFormData) => { const newNetworkData = { ...data, - chainId: `${namespace}${data.chainId}`, - networkType, - isDefault: false, + namespace, }; - setNetworksData([...networksData, newNetworkData]); - await storeNetworkData(newNetworkData); + + const mnemonicServer = await getInternetCredentials('mnemonicServer'); + const mnemonic = mnemonicServer && mnemonicServer.password; + + if (!mnemonic) { + throw new Error('Mnemonic not found'); + } + + const hdNode = HDNode.fromMnemonic(mnemonic); + + const hdPath = `m/44'/${newNetworkData.coinType}'/0'/0/0`; + const node = hdNode.derivePath(hdPath); + let address; + + switch (newNetworkData.namespace) { + case EIP155: + address = node.address; + break; + + case COSMOS: + address = ( + await getCosmosAccounts( + mnemonic, + hdPath, + newNetworkData.addressPrefix, + ) + ).data.address; + break; + + default: + throw new Error('Unsupported namespace'); + } + + const accountInfo = `${hdPath},${node.privateKey},${node.publicKey},${address}`; + + const retrievedNetworksData = await storeNetworkData(newNetworkData); + setNetworksData(retrievedNetworksData); + + await Promise.all([ + setInternetCredentials( + `accounts/${newNetworkData.namespace}:${newNetworkData.chainId}/0`, + '_', + accountInfo, + ), + setInternetCredentials( + `addAccountCounter/${newNetworkData.namespace}:${newNetworkData.chainId}`, + '_', + '1', + ), + setInternetCredentials( + `accountIndices/${newNetworkData.namespace}:${newNetworkData.chainId}`, + '_', + '0', + ), + ]); navigation.navigate('Laconic'); }, - [navigation, networkType, networksData, setNetworksData], + [navigation, namespace, setNetworksData], ); return ( @@ -123,8 +180,25 @@ const AddNetwork = () => { )} /> + ( + <> + onChange(value)} + /> + {errors.coinType?.message} + + )} + /> - {networkType === 'eth' ? ( + {namespace === EIP155 ? ( { )} /> - ( - <> - onChange(value)} - /> - {errors.coinType?.message} - - )} - /> )}