diff --git a/src/App.tsx b/src/App.tsx index fd8c034..7392606 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -45,6 +45,7 @@ import SignRequestEmbed from "./screens/SignRequestEmbed"; import useAddAccountEmbed from "./hooks/useAddAccountEmbed"; import useExportPKEmbed from "./hooks/useExportPrivateKeyEmbed"; import { SignTxEmbed } from "./screens/SignTxEmbed"; +import useAddNetworkEmbed from "./hooks/useAddNetworkEmbed"; const Stack = createStackNavigator(); @@ -287,6 +288,7 @@ const App = (): React.JSX.Element => { useWebViewHandler(); useAddAccountEmbed(); useExportPKEmbed(); + useAddNetworkEmbed(); return ( diff --git a/src/hooks/useAddNetworkEmbed.ts b/src/hooks/useAddNetworkEmbed.ts new file mode 100644 index 0000000..7ec6357 --- /dev/null +++ b/src/hooks/useAddNetworkEmbed.ts @@ -0,0 +1,39 @@ +import { useEffect } from 'react'; + +import { addNewNetwork } from '../utils/accounts'; +import { ADD_NETWORK } from '../utils/constants'; + +const REACT_APP_ALLOWED_URLS = import.meta.env.REACT_APP_ALLOWED_URLS; + +const useAddNetworkEmbed = () => { + useEffect(() => { + const handleAddNetwork = async (event: MessageEvent) => { + if (event.data.type !== ADD_NETWORK) return; + + if (!REACT_APP_ALLOWED_URLS) { + console.log('Allowed URLs are not set'); + return; + } + + const allowedUrls = REACT_APP_ALLOWED_URLS.split(',').map(url => url.trim()); + + if (!allowedUrls.includes(event.origin)) { + console.log('Unauthorized app.'); + return; + } + + try { + const { networkData } = event.data; + await addNewNetwork(networkData); + } catch (err) { + console.error('Error preparing sign request:', err); + } + }; + + window.addEventListener('message', handleAddNetwork); + return () => window.removeEventListener('message', handleAddNetwork); + }, []); + +} + +export default useAddNetworkEmbed; diff --git a/src/screens/AddNetwork.tsx b/src/screens/AddNetwork.tsx index 338ec3f..f662c77 100644 --- a/src/screens/AddNetwork.tsx +++ b/src/screens/AddNetwork.tsx @@ -1,8 +1,6 @@ import React, { useCallback, useEffect, useState } from "react"; import { useForm, Controller, useWatch, FieldErrors } from "react-hook-form"; import { TextInput, HelperText } from "react-native-paper"; - -import { HDNode } from "ethers/lib/utils"; import { chains } from "chain-registry"; import { useDebouncedCallback } from "use-debounce"; import { z } from "zod"; @@ -10,27 +8,21 @@ import { z } from "zod"; import { NativeStackNavigationProp } from "@react-navigation/native-stack"; import { useNavigation } from "@react-navigation/native"; import { zodResolver } from "@hookform/resolvers/zod"; +import { Divider, Grid } from "@mui/material"; +import { LoadingButton } from "@mui/lab"; import { StackParamsList } from "../types"; import { SelectNetworkType } from "../components/SelectNetworkType"; -import { addAccountsForNetwork, getNextAccountId, storeNetworkData } from "../utils/accounts"; +import { addAccountsForNetwork, addNewNetwork, getNextAccountId, storeNetworkData } from "../utils/accounts"; import { useNetworks } from "../context/NetworksContext"; import { - COSMOS, EIP155, CHAINID_DEBOUNCE_DELAY, EMPTY_FIELD_ERROR, INVALID_URL_ERROR, IS_NUMBER_REGEX, } from "../utils/constants"; -import { getCosmosAccountByHDPath } from "../utils/accounts"; import ETH_CHAINS from "../assets/ethereum-chains.json"; -import { - getInternetCredentials, - setInternetCredentials, -} from "../utils/key-store"; -import { Divider, Grid } from "@mui/material"; -import { LoadingButton } from "@mui/lab"; import { Layout } from "../components/Layout"; const ethNetworkDataSchema = z.object({ @@ -143,42 +135,8 @@ const AddNetwork = () => { isDefault: false, }; - const mnemonicServer = await getInternetCredentials("mnemonicServer"); - const mnemonic = mnemonicServer; + const retrievedNetworksData = await addNewNetwork(newNetworkData); - 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 getCosmosAccountByHDPath( - mnemonic, - hdPath, - (newNetworkData as z.infer) - .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); // Get number of accounts in first network @@ -190,24 +148,6 @@ const AddNetwork = () => { (network) => network.chainId === newNetworkData.chainId, ); - 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", - ), - ]); - await addAccountsForNetwork(selectedNetwork!, nextAccountId - 1); navigation.navigate("Home"); diff --git a/src/utils/accounts.ts b/src/utils/accounts.ts index b65e63d..73f5a27 100644 --- a/src/utils/accounts.ts +++ b/src/utils/accounts.ts @@ -160,6 +160,66 @@ const addAccountFromHDPath = async ( } }; +const addNewNetwork = async ( + newNetworkData: NetworksFormData +): Promise => { + const mnemonicServer = await getInternetCredentials("mnemonicServer"); + const mnemonic = mnemonicServer; + + 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 getCosmosAccountByHDPath( + mnemonic, + hdPath, + newNetworkData.addressPrefix, + ) + ).data.address; + break; + + default: + throw new Error("Unsupported namespace"); + } + + const accountInfo = `${hdPath},${node.privateKey},${node.publicKey},${address}`; + + 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", + ), + ]); + + const retrievedNetworksData = await storeNetworkData(newNetworkData); + + return retrievedNetworksData; +} + const storeNetworkData = async ( networkData: NetworksFormData, ): Promise => { @@ -180,11 +240,13 @@ const storeNetworkData = async ( networkId: String(networkId), }, ]; + await setInternetCredentials( 'networks', '_', JSON.stringify(updatedNetworks), ); + return updatedNetworks; }; @@ -194,7 +256,9 @@ const retrieveNetworksData = async (): Promise => { if(!networks){ return []; } + const parsedNetworks: NetworksDataState[] = JSON.parse(networks); + return parsedNetworks; }; @@ -217,6 +281,7 @@ export const retrieveAccountsForNetwork = async ( address, hdPath: path, }; + return account; }), ); @@ -234,6 +299,7 @@ const retrieveAccounts = async ( if (!accountIndices) { return; } + const loadedAccounts = await retrieveAccountsForNetwork( `${currentNetworkData.namespace}:${currentNetworkData.chainId}`, accountIndices, @@ -382,4 +448,5 @@ export { getNextAccountId, updateAccountCounter, getCosmosAccountByHDPath, + addNewNetwork }; diff --git a/src/utils/constants.ts b/src/utils/constants.ts index a657f5a..bb1db15 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -98,6 +98,7 @@ export const REQUEST_ACCOUNT_PK = 'REQUEST_ACCOUNT_PK'; export const REQUEST_ADD_ACCOUNT = 'REQUEST_ADD_ACCOUNT'; export const AUTO_SIGN_IN = 'AUTO_SIGN_IN'; export const CHECK_BALANCE = 'CHECK_BALANCE'; +export const ADD_NETWORK = 'ADD_NETWORK'; // iframe response types export const COSMOS_ACCOUNTS_RESPONSE = 'COSMOS_ACCOUNTS_RESPONSE';