Create hook for adding network (#34)
Part of https://www.notion.so/Create-stacks-for-mainnet-1f2a6b22d4728034be4be2c51decf94e Co-authored-by: IshaVenikar <ishavenikar7@gmail.com> Reviewed-on: #34 Co-authored-by: ishavenikar <ishavenikar@noreply.git.vdb.to> Co-committed-by: ishavenikar <ishavenikar@noreply.git.vdb.to>
This commit is contained in:
parent
fe3d2411a2
commit
d0623be1c3
@ -5,7 +5,5 @@ REACT_APP_DEFAULT_GAS_PRICE=0.025
|
||||
REACT_APP_GAS_ADJUSTMENT=2
|
||||
REACT_APP_LACONICD_RPC_URL=https://laconicd-sapo.laconic.com
|
||||
|
||||
REACT_APP_ZENITHD_RPC_URL=
|
||||
|
||||
# Example: https://example-url-1.com,https://example-url-2.com
|
||||
REACT_APP_ALLOWED_URLS=
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "web-wallet",
|
||||
"version": "0.1.6",
|
||||
"version": "0.1.7",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@cerc-io/registry-sdk": "^0.2.5",
|
||||
|
@ -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 useCreateNetwork from "./hooks/useCreateNetwork";
|
||||
|
||||
const Stack = createStackNavigator<StackParamsList>();
|
||||
|
||||
@ -287,6 +288,7 @@ const App = (): React.JSX.Element => {
|
||||
useWebViewHandler();
|
||||
useAddAccountEmbed();
|
||||
useExportPKEmbed();
|
||||
useCreateNetwork();
|
||||
|
||||
return (
|
||||
<Surface style={styles.appSurface}>
|
||||
|
99
src/hooks/useCreateNetwork.ts
Normal file
99
src/hooks/useCreateNetwork.ts
Normal file
@ -0,0 +1,99 @@
|
||||
import { useEffect, useCallback } from "react";
|
||||
|
||||
import { addNewNetwork, createWallet, checkNetworkForChainID, isWalletCreated } from "../utils/accounts";
|
||||
import { useNetworks } from "../context/NetworksContext";
|
||||
import { NETWORK_ADDED_RESPONSE, NETWORK_ADD_FAILED_RESPONSE, NETWORK_ALREADY_EXISTS_RESPONSE, REQUEST_ADD_NETWORK } from "../utils/constants";
|
||||
import { NetworksFormData } from "../types";
|
||||
import { sendMessage } from "../utils/misc";
|
||||
|
||||
const REACT_APP_ALLOWED_URLS = import.meta.env.REACT_APP_ALLOWED_URLS;
|
||||
|
||||
const useCreateNetwork = () => {
|
||||
const { networksData, setNetworksData } = useNetworks();
|
||||
|
||||
const getOrCreateNetwork = useCallback(
|
||||
async (chainId: string, networkData: NetworksFormData, sourceOrigin?: string) => {
|
||||
if (!sourceOrigin) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const isCreated = await isWalletCreated();
|
||||
|
||||
if (!isCreated) {
|
||||
console.log("Wallet not found, creating wallet...");
|
||||
await createWallet(networksData);
|
||||
}
|
||||
|
||||
const isNetworkPresent = await checkNetworkForChainID(chainId);
|
||||
|
||||
if (!isNetworkPresent) {
|
||||
console.log("ChainId not found. Adding network");
|
||||
|
||||
const resolvedNetworkData = {
|
||||
chainId,
|
||||
namespace: networkData.namespace,
|
||||
networkName: networkData.networkName,
|
||||
rpcUrl: networkData.rpcUrl,
|
||||
blockExplorerUrl: networkData.blockExplorerUrl || "",
|
||||
addressPrefix: networkData.addressPrefix || "",
|
||||
coinType: networkData.coinType,
|
||||
nativeDenom: networkData.nativeDenom || "",
|
||||
gasPrice: networkData.gasPrice || String(import.meta.env.REACT_APP_DEFAULT_GAS_PRICE),
|
||||
currencySymbol: networkData.currencySymbol || "",
|
||||
isDefault: false,
|
||||
};
|
||||
|
||||
const retrievedNetworksData = await addNewNetwork(resolvedNetworkData);
|
||||
setNetworksData(retrievedNetworksData);
|
||||
|
||||
sendMessage(window.parent, NETWORK_ADDED_RESPONSE, {
|
||||
type: NETWORK_ADDED_RESPONSE,
|
||||
chainId
|
||||
}, sourceOrigin);
|
||||
} else {
|
||||
sendMessage(window.parent, NETWORK_ALREADY_EXISTS_RESPONSE, {
|
||||
type: NETWORK_ALREADY_EXISTS_RESPONSE,
|
||||
chainId
|
||||
}, sourceOrigin);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error in getOrCreateNetwork:", error);
|
||||
sendMessage(window.parent, NETWORK_ADD_FAILED_RESPONSE, {
|
||||
type: NETWORK_ADD_FAILED_RESPONSE,
|
||||
message: error instanceof Error ? error.message : "Unknown error"
|
||||
}, sourceOrigin);
|
||||
}
|
||||
},
|
||||
[networksData, setNetworksData]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const handleCreateNetwork = async (event: MessageEvent) => {
|
||||
if (event.data.type !== REQUEST_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;
|
||||
}
|
||||
|
||||
const { chainId, networkData } = event.data;
|
||||
|
||||
await getOrCreateNetwork(chainId, networkData, event.origin);
|
||||
};
|
||||
|
||||
window.addEventListener("message", handleCreateNetwork);
|
||||
return () => {
|
||||
window.removeEventListener("message", handleCreateNetwork);
|
||||
};
|
||||
}, [getOrCreateNetwork]);
|
||||
};
|
||||
|
||||
export default useCreateNetwork;
|
@ -1,6 +1,6 @@
|
||||
import { useEffect, useCallback } from "react";
|
||||
|
||||
import { createWallet } from "../utils/accounts";
|
||||
import { createWallet, isWalletCreated } from "../utils/accounts";
|
||||
import { sendMessage } from "../utils/misc";
|
||||
import useAccountsData from "./useAccountsData";
|
||||
import { useNetworks } from "../context/NetworksContext";
|
||||
@ -16,14 +16,15 @@ const useGetOrCreateAccounts = () => {
|
||||
|
||||
// Wrap the function in useCallback to prevent recreation on each render
|
||||
const getOrCreateAccountsForChain = useCallback(async (chainId: string) => {
|
||||
let accountsData = await getAccountsData(chainId);
|
||||
const isCreated = await isWalletCreated();
|
||||
|
||||
if (accountsData.length === 0) {
|
||||
if (!isCreated) {
|
||||
console.log("Accounts not found, creating wallet...");
|
||||
await createWallet(networksData);
|
||||
accountsData = await getAccountsData(chainId);
|
||||
}
|
||||
|
||||
const accountsData = await getAccountsData(chainId);
|
||||
|
||||
// Update the AccountsContext with the new accounts
|
||||
setAccounts(accountsData);
|
||||
|
||||
|
1
src/import-meta-env.d.ts
vendored
1
src/import-meta-env.d.ts
vendored
@ -5,7 +5,6 @@ interface ImportMetaEnv {
|
||||
readonly REACT_APP_DEFAULT_GAS_PRICE: string;
|
||||
readonly REACT_APP_GAS_ADJUSTMENT: string;
|
||||
readonly REACT_APP_LACONICD_RPC_URL: string;
|
||||
readonly REACT_APP_ZENITHD_RPC_URL: string;
|
||||
readonly REACT_APP_ALLOWED_URLS: string;
|
||||
}
|
||||
|
||||
|
@ -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 { addNewNetwork } 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,73 +135,10 @@ 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<typeof cosmosNetworkDataSchema>)
|
||||
.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
|
||||
const nextAccountId = await getNextAccountId(
|
||||
`${retrievedNetworksData[0].namespace}:${retrievedNetworksData[0].chainId}`,
|
||||
);
|
||||
|
||||
const selectedNetwork = retrievedNetworksData.find(
|
||||
(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");
|
||||
},
|
||||
[navigation, namespace, setNetworksData],
|
||||
|
@ -64,7 +64,7 @@ export type NetworksFormData = {
|
||||
namespace: string;
|
||||
nativeDenom?: string;
|
||||
addressPrefix?: string;
|
||||
coinType?: string;
|
||||
coinType: string;
|
||||
gasPrice?: string;
|
||||
isDefault: boolean;
|
||||
};
|
||||
|
@ -160,6 +160,77 @@ const addAccountFromHDPath = async (
|
||||
}
|
||||
};
|
||||
|
||||
const addNewNetwork = async (
|
||||
newNetworkData: NetworksFormData
|
||||
): Promise<NetworksDataState[]> => {
|
||||
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);
|
||||
|
||||
// Get number of accounts in first network
|
||||
const nextAccountId = await getNextAccountId(
|
||||
`${retrievedNetworksData[0].namespace}:${retrievedNetworksData[0].chainId}`,
|
||||
);
|
||||
|
||||
const selectedNetwork = retrievedNetworksData.find(
|
||||
(network) => network.chainId === newNetworkData.chainId,
|
||||
);
|
||||
|
||||
await addAccountsForNetwork(selectedNetwork!, nextAccountId - 1);
|
||||
|
||||
return retrievedNetworksData;
|
||||
}
|
||||
|
||||
const storeNetworkData = async (
|
||||
networkData: NetworksFormData,
|
||||
): Promise<NetworksDataState[]> => {
|
||||
@ -180,21 +251,25 @@ const storeNetworkData = async (
|
||||
networkId: String(networkId),
|
||||
},
|
||||
];
|
||||
|
||||
await setInternetCredentials(
|
||||
'networks',
|
||||
'_',
|
||||
JSON.stringify(updatedNetworks),
|
||||
);
|
||||
|
||||
return updatedNetworks;
|
||||
};
|
||||
|
||||
const retrieveNetworksData = async (): Promise<NetworksDataState[]> => {
|
||||
const networks = await getInternetCredentials('networks');
|
||||
|
||||
if(!networks){
|
||||
if (!networks) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const parsedNetworks: NetworksDataState[] = JSON.parse(networks);
|
||||
|
||||
return parsedNetworks;
|
||||
};
|
||||
|
||||
@ -217,6 +292,7 @@ export const retrieveAccountsForNetwork = async (
|
||||
address,
|
||||
hdPath: path,
|
||||
};
|
||||
|
||||
return account;
|
||||
}),
|
||||
);
|
||||
@ -234,6 +310,7 @@ const retrieveAccounts = async (
|
||||
if (!accountIndices) {
|
||||
return;
|
||||
}
|
||||
|
||||
const loadedAccounts = await retrieveAccountsForNetwork(
|
||||
`${currentNetworkData.namespace}:${currentNetworkData.chainId}`,
|
||||
accountIndices,
|
||||
@ -368,6 +445,28 @@ const getCosmosAccountByHDPath = async (
|
||||
return { cosmosWallet, data };
|
||||
};
|
||||
|
||||
const checkNetworkForChainID = async (
|
||||
chainId: string,
|
||||
): Promise<boolean> => {
|
||||
const networks = await getInternetCredentials('networks');
|
||||
|
||||
if (!networks) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const networksData: NetworksFormData[] = JSON.parse(networks);
|
||||
|
||||
return networksData.some((network) => network.chainId === chainId);
|
||||
}
|
||||
|
||||
const isWalletCreated = async (
|
||||
): Promise<boolean> => {
|
||||
const mnemonicServer = await getInternetCredentials("mnemonicServer");
|
||||
const mnemonic = mnemonicServer;
|
||||
|
||||
return mnemonic !== null;
|
||||
};
|
||||
|
||||
export {
|
||||
createWallet,
|
||||
addAccount,
|
||||
@ -382,4 +481,7 @@ export {
|
||||
getNextAccountId,
|
||||
updateAccountCounter,
|
||||
getCosmosAccountByHDPath,
|
||||
addNewNetwork,
|
||||
checkNetworkForChainID,
|
||||
isWalletCreated
|
||||
};
|
||||
|
@ -18,30 +18,6 @@ export const DEFAULT_NETWORKS: NetworksFormData[] = [
|
||||
gasPrice: '0.001',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
chainId: 'zenith-testnet-stage0',
|
||||
networkName: 'zenithd stage 0 testnet',
|
||||
namespace: COSMOS,
|
||||
rpcUrl: import.meta.env.REACT_APP_ZENITHD_RPC_URL || '',
|
||||
blockExplorerUrl: '',
|
||||
nativeDenom: 'znt',
|
||||
addressPrefix: 'zenith',
|
||||
coinType: '118',
|
||||
gasPrice: '0.01',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
chainId: 'zenith-testnet-stage1',
|
||||
networkName: 'zenithd stage 1 testnet',
|
||||
namespace: COSMOS,
|
||||
rpcUrl: import.meta.env.REACT_APP_ZENITHD_RPC_URL || '',
|
||||
blockExplorerUrl: '',
|
||||
nativeDenom: 'znt',
|
||||
addressPrefix: 'zenith',
|
||||
coinType: '118',
|
||||
gasPrice: '0.01',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
chainId: 'laconic_9000-1',
|
||||
networkName: 'laconicd',
|
||||
@ -98,6 +74,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 REQUEST_ADD_NETWORK = 'REQUEST_ADD_NETWORK';
|
||||
|
||||
// iframe response types
|
||||
export const COSMOS_ACCOUNTS_RESPONSE = 'COSMOS_ACCOUNTS_RESPONSE';
|
||||
@ -109,3 +86,6 @@ export const ACCOUNT_PK_RESPONSE = 'ACCOUNT_PK_RESPONSE';
|
||||
export const ADD_ACCOUNT_RESPONSE = 'ADD_ACCOUNT_RESPONSE';
|
||||
export const WALLET_ACCOUNTS_DATA = 'WALLET_ACCOUNTS_DATA';
|
||||
export const IS_SUFFICIENT = 'IS_SUFFICIENT';
|
||||
export const NETWORK_ADDED_RESPONSE = "NETWORK_ADDED_RESPONSE";
|
||||
export const NETWORK_ALREADY_EXISTS_RESPONSE = "NETWORK_ALREADY_EXISTS_RESPONSE";
|
||||
export const NETWORK_ADD_FAILED_RESPONSE = "NETWORK_ADD_FAILED_RESPONSE";
|
||||
|
@ -10,7 +10,6 @@ echo "WALLET_CONNECT_ID: ${WALLET_CONNECT_ID}"
|
||||
echo "CERC_DEFAULT_GAS_PRICE: ${CERC_DEFAULT_GAS_PRICE}"
|
||||
echo "CERC_GAS_ADJUSTMENT: ${CERC_GAS_ADJUSTMENT}"
|
||||
echo "CERC_LACONICD_RPC_URL: ${CERC_LACONICD_RPC_URL}"
|
||||
echo "CERC_ZENITHD_RPC_URL: ${CERC_ZENITHD_RPC_URL}"
|
||||
echo "CERC_ALLOWED_URLS: ${CERC_ALLOWED_URLS}"
|
||||
|
||||
# Build with required env
|
||||
@ -18,7 +17,6 @@ export REACT_APP_WALLET_CONNECT_PROJECT_ID=$WALLET_CONNECT_ID
|
||||
export REACT_APP_DEFAULT_GAS_PRICE=$CERC_DEFAULT_GAS_PRICE
|
||||
export REACT_APP_GAS_ADJUSTMENT=$CERC_GAS_ADJUSTMENT
|
||||
export REACT_APP_LACONICD_RPC_URL=$CERC_LACONICD_RPC_URL
|
||||
export REACT_APP_ZENITHD_RPC_URL=$CERC_ZENITHD_RPC_URL
|
||||
export REACT_APP_ALLOWED_URLS=$CERC_ALLOWED_URLS
|
||||
|
||||
# Set env variables in build
|
||||
|
Loading…
Reference in New Issue
Block a user