Cleaning up code and adding comments

This commit is contained in:
valefar-on-discord
2024-03-25 22:42:46 -05:00
parent ddeebe6cf7
commit 71ce69a469
27 changed files with 374 additions and 236 deletions

View File

@@ -1,27 +1,34 @@
import { CssBaseline, ThemeProvider, StyledEngineProvider } from "@mui/material";
import { FC, ReactElement } from "react";
import { HashRouter, Route, Switch } from "react-router-dom";
import BTECContextWrapper from "./BTECContext";
import { OnlineDetector } from "./components/OnlineDetector";
import VersionFooter from "./components/VersionFooter";
import { paths } from "./constants";
import GlobalContextWrapper from "./GlobalContext";
import CreateMnemonic from "./pages/CreateMnemonic";
import Home from "./pages/Home";
import theme from "./theme";
import ConfigureValidatorKeys from "./pages/ConfigureValidatorKeys";
import MnemonicImport from "./pages/MnemonicImport";
import ConfigureWithdrawalAddress from "./pages/ConfigureWithdrawalAddress";
import { BTECImportPath, ConfigureBTECPath, ConfigureCreatePath, ConfigureExistingPath, CreateCredentialsPath, CreateKeysCreatePath, CreateKeysExistingPath, CreatePath, ExistingImportPath, FinishCreatePath, FinishCredentialsPath, FinishExistingPath } from "./constants";
import CreateValidatorKeys from "./pages/CreateValidatorKeys";
import KeyCreationContextWrapper from "./KeyCreationContext";
import BTECContextWrapper from "./BTECContext";
import FinishKeyGeneration from "./pages/FinishKeyGeneration";
import ConfigureValidatorKeys from "./pages/ConfigureValidatorKeys";
import ConfigureWithdrawalAddress from "./pages/ConfigureWithdrawalAddress";
import CreateCredentialsChange from "./pages/CreateCredentialsChange";
import CreateMnemonic from "./pages/CreateMnemonic";
import CreateValidatorKeys from "./pages/CreateValidatorKeys";
import FinishCredentialsGeneration from "./pages/FinishCredentialsGeneration";
import FinishKeyGeneration from "./pages/FinishKeyGeneration";
import Home from "./pages/Home";
import MnemonicImport from "./pages/MnemonicImport";
import theme from "./theme";
/**
* The React app top level including theme and routing.
* Routing for the application. Broken into four sections:
* - Primary home page
* - Routes for creating a mnemonic and validator keys
* - Routes for using an existing mnemonic to create validator keys
* - Routes for generating the credentials change
*
* @returns the react element containing the app
* Each of the three flows is wrapped in a React Context that will store
* the inputs of the user to be accessible across each page. This prevents
* prop drilling
*/
const App: FC = (): ReactElement => {
return (
@@ -40,10 +47,10 @@ const App: FC = (): ReactElement => {
{/* Create Mnemonic & Keys Flow */}
<KeyCreationContextWrapper>
<Switch>
<Route path={CreatePath} children={() => <CreateMnemonic />} />
<Route path={ConfigureCreatePath} children={() => <ConfigureValidatorKeys />} />
<Route path={CreateKeysCreatePath} children={() => <CreateValidatorKeys />} />
<Route path={FinishCreatePath} children={() => <FinishKeyGeneration />} />
<Route path={paths.CREATE_MNEMONIC} children={() => <CreateMnemonic />} />
<Route path={paths.CONFIGURE_CREATE} children={() => <ConfigureValidatorKeys />} />
<Route path={paths.CREATE_KEYS_CREATE} children={() => <CreateValidatorKeys />} />
<Route path={paths.FINISH_CREATE} children={() => <FinishKeyGeneration />} />
</Switch>
</KeyCreationContextWrapper>
@@ -51,10 +58,10 @@ const App: FC = (): ReactElement => {
{/* Import Mnemonic & Generate Keys Flow */}
<KeyCreationContextWrapper>
<Switch>
<Route path={ExistingImportPath} render={() => <MnemonicImport />} />
<Route path={ConfigureExistingPath} render={() => <ConfigureValidatorKeys />} />
<Route path={CreateKeysExistingPath} render={() => <CreateValidatorKeys />} />
<Route path={FinishExistingPath} render={() => <FinishKeyGeneration />} />
<Route path={paths.EXISTING_IMPORT} render={() => <MnemonicImport />} />
<Route path={paths.CONFIGURE_EXISTING} render={() => <ConfigureValidatorKeys />} />
<Route path={paths.CREATE_KEYS_EXISTING} render={() => <CreateValidatorKeys />} />
<Route path={paths.FINISH_EXISTING} render={() => <FinishKeyGeneration />} />
</Switch>
</KeyCreationContextWrapper>
@@ -62,10 +69,10 @@ const App: FC = (): ReactElement => {
{/* Update Withdrawal Credentials Flow */}
<BTECContextWrapper>
<Switch>
<Route path={BTECImportPath} render={() => <MnemonicImport />} />
<Route path={ConfigureBTECPath} render={() => <ConfigureWithdrawalAddress />} />
<Route path={CreateCredentialsPath} render={() => <CreateCredentialsChange />} />
<Route path={FinishCredentialsPath} render={() => <FinishCredentialsGeneration />} />
<Route path={paths.BTEC_IMPORT} render={() => <MnemonicImport />} />
<Route path={paths.CONFIGURE_BTEC} render={() => <ConfigureWithdrawalAddress />} />
<Route path={paths.CREATE_CREDENTIALS} render={() => <CreateCredentialsChange />} />
<Route path={paths.FINISH_CREDENTIALS} render={() => <FinishCredentialsGeneration />} />
</Switch>
</BTECContextWrapper>
</Route>

View File

@@ -30,6 +30,9 @@ export const BTECContext = createContext<BTECContextType>({
setWithdrawalAddress: () => {},
});
/**
* Context for making the withdrawal credentials change
*/
const BTECContextWrapper = ({ children }: { children: React.ReactNode}) => {
const [btecIndices, setBTECIndices] = useState<string>("");
const [btecCredentials, setBTECCredentials] = useState<string>("");

View File

@@ -30,6 +30,9 @@ export const KeyCreationContext = createContext<KeyCreationContextType>({
setWithdrawalAddress: () => {},
});
/**
* Context for generating a validator key for both using an existing mnemonic or a new one
*/
const KeyCreationContextWrapper = ({ children }: { children: React.ReactNode}) => {
const [folderLocation, setFolderLocation] = useState<string>("");
const [index, setIndex] = useState<number>(0);

View File

@@ -1,16 +1,26 @@
import { Button, Typography } from "@mui/material";
import { OpenDialogOptions, OpenDialogReturnValue } from 'electron';
import { useState } from "react";
import { errors } from "../constants";
interface FolderSelectorParams {
onFolderSelect: (folder: string) => void;
}
/**
* Component to select a folder and will use onFolderSelect param to provide
* selected folder to parent
* @param onFolderSelect callback to provide selected folder
*/
const FolderSelector = ({ onFolderSelect }: FolderSelectorParams) => {
const [displayFolderPicker, setDisplayFolderPicker] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
/**
* Called upon selecting a folder. Will verify the folder exists and is writable
* @param folderPath the path of the folder to verify
*/
const verifyFolder = (folderPath: string) => {
window.bashUtils.doesDirectoryExist(folderPath)
.then((exists) => {
@@ -30,6 +40,9 @@ const FolderSelector = ({ onFolderSelect }: FolderSelectorParams) => {
});
};
/**
* Will open a dialog to allow the user to select a folder
*/
const chooseFolder = () => {
const options: OpenDialogOptions = {
properties: ['openDirectory']

View File

@@ -2,14 +2,16 @@ interface LoaderParams {
message: string;
}
const Loader = ({ message }: LoaderParams) => {
return (
<div className="tw-flex tw-flex-col tw-gap-8 tw-items-center">
<div className="tw-px-12">{message}</div>
/**
* Simple loading spinner component with dynamic message
* @param message message to display to the user
*/
const Loader = ({ message }: LoaderParams) => (
<div className="tw-flex tw-flex-col tw-gap-8 tw-items-center">
<div className="tw-px-12">{message}</div>
<div className="tw-border-4 tw-border-solid tw-border-gray1 tw-border-t-mediumBlue tw-rounded-full tw-w-[50px] tw-h-[50px] tw-animate-LoaderSpin" />
</div>
)
};
<div className="tw-border-4 tw-border-solid tw-border-gray1 tw-border-t-mediumBlue tw-rounded-full tw-w-[50px] tw-h-[50px] tw-animate-LoaderSpin" />
</div>
);
export default Loader;

View File

@@ -1,25 +1,14 @@
import {
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Grid,
Typography,
styled,
} from "@mui/material";
import { Button, Typography } from "@mui/material";
import PermScanWifiIcon from "@mui/icons-material/PermScanWifi";
import React from "react";
import OnlineWarningModal from "../modals/OnlineWarningModal";
/**
* This will add an event listener to detect the users internet connectivity.
* If active, a pulsing warning icon with text will appear on the screen that
* when clicked will show a dialog warning the user of the danger of internet
* when clicked will show a modal to warn the user of the danger of internet
* connectivity.
*
* @returns the warning and dialog component to render if necessary
*/
export const OnlineDetector = () => {
const [open, setOpen] = React.useState<boolean>(false);

View File

@@ -1,8 +1,10 @@
import { Grid, TextField } from "@mui/material";
import { Dispatch, ReactNode, SetStateAction, useContext, useEffect, useMemo, useRef, useState } from "react";
import { GlobalContext } from "../GlobalContext";
import { Network } from "../types";
import { Dispatch, SetStateAction, useContext, useRef, useState } from "react";
import { errors } from "../constants";
import { GlobalContext } from "../GlobalContext";
import { KeyCreationContext } from "../KeyCreationContext";
import { Network } from "../types";
interface VerifyMnemonicParams {
hasError: boolean;
@@ -11,13 +13,22 @@ interface VerifyMnemonicParams {
onVerifyMnemonic: () => void;
}
/**
* Creates a grid of inputs if on mainnet or a single textbox for ease of testing.
* The user fills the form with the mnemonic and the input will be provided to the
* onVerifyMnemonic for futher action
*
* mnemonicToVerify is provided as a param instead of existing only in this component
* so the parent can fill the value if the user goes back in navigation
*/
const VerifyMnemonic = ({
hasError = false,
mnemonicToVerify,
setMnemonicToVerify,
onVerifyMnemonic,
}: VerifyMnemonicParams) => {
const { mnemonic, network } = useContext(GlobalContext);
const { mnemonic } = useContext(KeyCreationContext);
const { network } = useContext(GlobalContext);
const [mnemonicToVerifyArray, setMnemonicToVerifyArray] = useState<string[]>(
mnemonicToVerify ? mnemonicToVerify.split(' ') : Array(24).fill(''));
const inputRefs = useRef<HTMLInputElement[]>([]);
@@ -41,8 +52,11 @@ const VerifyMnemonic = ({
}
};
/**
* Will focus on a new input depending on the pressed key
* @param index the current input index
*/
const handleKeysForWord = (index: number) => (e: React.KeyboardEvent<HTMLInputElement>) => {
// Navigate phrase confirmation with spacebar or arrowkeys
let nextFocus = index;
const currentTextFieldValue = mnemonicToVerifyArray[index];

View File

@@ -1,32 +1,15 @@
import { Typography } from "@mui/material";
import styled from "styled-components";
declare var VERSION: string;
declare var COMMITHASH: string;
const SoftText = styled(Typography)`
color: gray;
text-align: center;
font-size: 10px;
`;
const Container = styled.div`
position: fixed;
bottom: 35;
width: 100%;
`;
/**
* This component is a footer used to display the version and commit hash.
*
* @returns the footer component containing the version and commit hash
* Footer to display the version and commit hash
*/
const VersionFooter = () => {
return(
<Container>
<SoftText>Version: {VERSION} - Commit Hash: {COMMITHASH}</SoftText>
</Container>
)
}
const VersionFooter = () => (
<div className="tw-fixed tw-bottom-9 tw-w-full">
<Typography className="tw-text-gray tw-text-center tw-text-xxs">Version: {VERSION} - Commit Hash: {COMMITHASH}</Typography>
</div>
);
export default VersionFooter;

View File

@@ -1,7 +1,8 @@
import { ReactNode, useContext } from "react";
import { GlobalContext } from "../GlobalContext";
import { Step, StepLabel, Stepper, Typography } from "@mui/material";
import { stepLabels } from "../constants";
import { GlobalContext } from "../GlobalContext";
import { StepKey } from "../types";
interface WizardWrapperParams {
@@ -12,6 +13,15 @@ interface WizardWrapperParams {
title: string;
}
/**
* Wrapper of a page to display the network, title, stepper, and action bar buttons.
*
* @param actionBarItems A list of buttons to display
* @param activeTimelineIndex The index of the timelineItems array that is active
* @param children The inner content of the page
* @param timelineItems A list of steps to display
* @param title The title to appear at the top of the page
*/
const WizardWrapper = ({
actionBarItems,
activeTimelineIndex,

View File

@@ -53,17 +53,19 @@ export const CreateMnemonicFlow = [StepKey.MnemonicGeneration, StepKey.KeyConfig
export const ExistingMnemonicFlow = [StepKey.MnemonicImport, StepKey.KeyConfiguration, StepKey.KeyGeneration, StepKey.Finish];
export const BTECFlow = [StepKey.MnemonicImport, StepKey.BTECConfiguration, StepKey.BTECGeneration, StepKey.FinishBTEC];
export const CreatePath = "/create";
export const ConfigureCreatePath = "/configure-create";
export const CreateKeysCreatePath = "/create-keys";
export const FinishCreatePath ="/finish-create";
export const paths = {
CREATE_MNEMONIC: "/create",
CONFIGURE_CREATE: "/configure-create",
CREATE_KEYS_CREATE: "/create-keys",
FINISH_CREATE:"/finish-create",
export const ExistingImportPath = "/import-existing";
export const ConfigureExistingPath = "/configure-existing";
export const CreateKeysExistingPath = "/create-existing-keys";
export const FinishExistingPath = "/finish-existing";
EXISTING_IMPORT: "/import-existing",
CONFIGURE_EXISTING: "/configure-existing",
CREATE_KEYS_EXISTING: "/create-existing-keys",
FINISH_EXISTING: "/finish-existing",
export const BTECImportPath = "/import-btec";
export const ConfigureBTECPath = "/configure-btec";
export const CreateCredentialsPath = "/create-btec";
export const FinishCredentialsPath = "/finish-btec";
BTEC_IMPORT: "/import-btec",
CONFIGURE_BTEC: "/configure-btec",
CREATE_CREDENTIALS: "/create-btec",
FINISH_CREDENTIALS: "/finish-btec",
};

View File

@@ -1,26 +1,25 @@
import { Dispatch, SetStateAction, createContext, useState } from "react";
import { Network } from "./types";
interface GlobalContextType {
mnemonic: string;
setMnemonic: Dispatch<SetStateAction<string>>;
network: Network;
setNetwork: Dispatch<SetStateAction<Network>>;
}
export const GlobalContext = createContext<GlobalContextType>({
mnemonic: "",
setMnemonic: () => {},
network: Network.MAINNET,
setNetwork: () => {},
});
/**
* Global context for the network which is used across the application
*/
const GlobalContextWrapper = ({ children }: { children: React.ReactNode}) => {
const [mnemonic, setMnemonic] = useState<string>("");
const [network, setNetwork] = useState<Network>(Network.MAINNET);
return (
<GlobalContext.Provider value={{ mnemonic, setMnemonic, network, setNetwork }}>
<GlobalContext.Provider value={{ network, setNetwork }}>
{children}
</GlobalContext.Provider>
);

View File

@@ -1,3 +1,8 @@
/**
* Helper function to clean the mnemonic of invalid characters and extraneous spacing
* @param mnemonic the mnemonic to clean
* @returns the cleaned mnemonic
*/
export const cleanMnemonic = (mnemonic: String): string => {
const punctuationRemoved = mnemonic.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, " ");
const singleSpace = punctuationRemoved.replace(/\s\s+/g, " ");

View File

@@ -1,6 +1,7 @@
import * as ReactDOM from "react-dom";
import App from "./App";
import 'typeface-roboto';
import App from "./App";
import './index.css';
// We find our app DOM element as before

View File

@@ -7,9 +7,10 @@ import {
RadioGroup,
Typography,
} from "@mui/material";
import { Network } from "../types";
import { useContext, useEffect, useState } from "react";
import { GlobalContext } from "../GlobalContext";
import { Network } from "../types";
import WagyuModal from "./WagyuModal";
interface NetworkPickerModalParams {
@@ -17,6 +18,9 @@ interface NetworkPickerModalParams {
showModal: boolean;
}
/**
* Modal to allow the user to pick the Ethereum Network
*/
const NetworkPickerModal = ({onClose, showModal}: NetworkPickerModalParams) => {
const { network, setNetwork } = useContext(GlobalContext);
const [formNetwork, setFormNetwork] = useState<Network>(Network.MAINNET);

View File

@@ -1,4 +1,5 @@
import { Button, Typography } from "@mui/material";
import WagyuModal from "./WagyuModal";
interface OlineWarningModalParams {
@@ -7,49 +8,50 @@ interface OlineWarningModalParams {
open: boolean;
}
const OnlineWarningModal = ({ onClose, onHideWarning, open }: OlineWarningModalParams) => {
return (
<WagyuModal
className="tw-w-[600px]"
open={open}
>
<div className="tw-py-8 tw-px-4">
<div className="tw-text-3xl tw-text-center tw-mb-8">Internet Connection Detected</div>
<div className="tw-text-left tw-min-h-[250px]">
<Typography className="tw-mb-2" variant="body1">
Being connected to the internet while using this tool drastically increases the risk of exposing your Secret Recovery Phrase.
</Typography>
<Typography variant="body1">
You can avoid this risk by having a live OS such as Tails installed on a USB drive and run on a computer with network capabilities disabled.
</Typography>
/**
* Modal to display to the user to explain the risks of using this tool with network connectivity
*/
const OnlineWarningModal = ({ onClose, onHideWarning, open }: OlineWarningModalParams) => (
<WagyuModal
className="tw-w-[600px]"
open={open}
>
<div className="tw-py-8 tw-px-4">
<div className="tw-text-3xl tw-text-center tw-mb-8">Internet Connection Detected</div>
<div className="tw-text-left tw-min-h-[250px]">
<Typography className="tw-mb-2" variant="body1">
Being connected to the internet while using this tool drastically increases the risk of exposing your Secret Recovery Phrase.
</Typography>
<Typography variant="body1">
You can avoid this risk by having a live OS such as Tails installed on a USB drive and run on a computer with network capabilities disabled.
</Typography>
<Typography className="tw-mt-6 tw-mb-2" variant="body1">
You can visit https://tails.net/install/ for instructions on how to download, install, and run Tails on a USB device.
</Typography>
<Typography variant="body1">
If you have any questions you can get help at https://dsc.gg/ethstaker
</Typography>
</div>
<div>
<Button
className="tw-mr-2"
color="secondary"
onClick={() => onHideWarning()}
variant="contained"
>
Hide Warning
</Button>
<Button
color="primary"
onClick={() => onClose()}
variant="contained"
>
Close
</Button>
</div>
<Typography className="tw-mt-6 tw-mb-2" variant="body1">
You can visit https://tails.net/install/ for instructions on how to download, install, and run Tails on a USB device.
</Typography>
<Typography variant="body1">
If you have any questions you can get help at https://dsc.gg/ethstaker
</Typography>
</div>
</WagyuModal>
);
};
<div>
<Button
className="tw-mr-2"
color="secondary"
onClick={() => onHideWarning()}
variant="contained"
>
Hide Warning
</Button>
<Button
color="primary"
onClick={() => onClose()}
variant="contained"
>
Close
</Button>
</div>
</div>
</WagyuModal>
);
export default OnlineWarningModal;

View File

@@ -1,4 +1,5 @@
import { Button, Tooltip } from "@mui/material";
import { ReuseMnemonicAction } from "../types";
import WagyuModal from "./WagyuModal";
@@ -8,35 +9,38 @@ interface ReuseMnemonicActionModalParams {
showModal: boolean;
}
const ReuseMnemonicActionModal = ({ onClose, onSubmit, showModal}: ReuseMnemonicActionModalParams) => {
return (
<WagyuModal
className="tw-w-[560px] tw-h-[260px]"
open={showModal}
onClose={onClose}
>
<div className="tw-flex tw-flex-col tw-h-full tw-my-7">
<div className="tw-text-2xl">How would you like to use your existing secret recovery phrase?</div>
/**
* Modal for the user to pick which action they would like to take when reusing a mnemonic
*
* Options are: Generate existing keys or Generate BLS change
*/
const ReuseMnemonicActionModal = ({ onClose, onSubmit, showModal}: ReuseMnemonicActionModalParams) => (
<WagyuModal
className="tw-w-[560px] tw-h-[260px]"
open={showModal}
onClose={onClose}
>
<div className="tw-flex tw-flex-col tw-h-full tw-my-7">
<div className="tw-text-2xl">How would you like to use your existing secret recovery phrase?</div>
<div className="tw-grow" />
<div className="tw-grow" />
<div className="tw-flex tw-flex-col tw-gap-2 tw-align-middle">
<div>
<Button variant="contained" color="primary" onClick={() => onSubmit(ReuseMnemonicAction.RegenerateKeys)}>
Generate existing or new validator keys
<div className="tw-flex tw-flex-col tw-gap-2 tw-align-middle">
<div>
<Button variant="contained" color="primary" onClick={() => onSubmit(ReuseMnemonicAction.RegenerateKeys)}>
Generate existing or new validator keys
</Button>
</div>
<div>
<Tooltip title="If you initially created your validator keys without adding a withdrawal address, you can generate this BLS to execution change to add one once.">
<Button variant="contained" color="primary" onClick={() => onSubmit(ReuseMnemonicAction.GenerateBLSToExecutionChange)}>
Generate your BLS to execution change<br />(Add a withdrawal address)
</Button>
</div>
<div>
<Tooltip title="If you initially created your validator keys without adding a withdrawal address, you can generate this BLS to execution change to add one once.">
<Button variant="contained" color="primary" onClick={() => onSubmit(ReuseMnemonicAction.GenerateBLSToExecutionChange)}>
Generate your BLS to execution change<br />(Add a withdrawal address)
</Button>
</Tooltip>
</div>
</Tooltip>
</div>
</div>
</WagyuModal>
)
};
</div>
</WagyuModal>
);
export default ReuseMnemonicActionModal;

View File

@@ -5,17 +5,18 @@ interface WagyuModalParams {
children: React.ReactNode;
}
const WagyuModal = ({ children, className, onClose, open}: WagyuModalParams & ModalProps) => {
return (
<Modal
open={open}
onClose={onClose}
>
<div className={`tw-flex tw-flex-col tw-bg-backgroundLight tw-rounded-3xl tw-text-center tw-m-auto tw-mt-[150px] ${className || ""}`}>
{children}
</div>
</Modal>
);
};
/**
* Wrapper for modal usages to keep consistent styling.
*/
const WagyuModal = ({ children, className, onClose, open}: WagyuModalParams & ModalProps) => (
<Modal
open={open}
onClose={onClose}
>
<div className={`tw-flex tw-flex-col tw-bg-backgroundLight tw-rounded-3xl tw-text-center tw-m-auto tw-mt-[150px] ${className || ""}`}>
{children}
</div>
</Modal>
);
export default WagyuModal;

View File

@@ -1,10 +1,18 @@
import { Button, TextField, Tooltip, Typography } from "@mui/material";
import WizardWrapper from "../components/WizardWrapper";
import { useContext, useEffect, useState } from "react";
import { useHistory } from "react-router-dom";
import { ConfigureExistingPath, CreateKeysCreatePath, CreateKeysExistingPath, CreateMnemonicFlow, CreatePath, ExistingImportPath, ExistingMnemonicFlow, errors, tooltips } from "../constants";
import WizardWrapper from "../components/WizardWrapper";
import { CreateMnemonicFlow, ExistingMnemonicFlow, errors, paths, tooltips } from "../constants";
import { KeyCreationContext } from "../KeyCreationContext";
/**
* Form to provide number of keys, index, password, and optional withdrawal address necessary to
* complete the validator key creation process.
*
* User will provide the necessary inputs and a verification of the password will be done before
* they can continue the flow
*/
const ConfigureValidatorKeys = () => {
const {
mnemonic,
@@ -14,7 +22,7 @@ const ConfigureValidatorKeys = () => {
setWithdrawalAddress,
} = useContext(KeyCreationContext);
const history = useHistory();
const usingExistingFlow = history.location.pathname === ConfigureExistingPath;
const usingExistingFlow = history.location.pathname === paths.CONFIGURE_EXISTING;
const [passwordToVerify, setPasswordToVerify] = useState("");
const [verifyPassword, setVerifyPassword] = useState(false);
@@ -34,7 +42,7 @@ const ConfigureValidatorKeys = () => {
useEffect(() => {
if (!mnemonic) {
history.replace(usingExistingFlow ? ExistingImportPath : CreatePath);
history.replace(usingExistingFlow ? paths.EXISTING_IMPORT : paths.CREATE_MNEMONIC);
}
}, []);
@@ -56,6 +64,10 @@ const ConfigureValidatorKeys = () => {
setInputWithdrawalAddress(e.target.value.trim());
};
/**
* Validates each value simultaneously and if there are no errors will show the
* user the password verification input
*/
const validateInputs = async () => {
let isError = false;
@@ -103,6 +115,9 @@ const ConfigureValidatorKeys = () => {
}
};
/**
* Verifies the passwords match and will move the user to the next step in the flow if so
*/
const checkPassword = () => {
if (inputPassword.localeCompare(passwordToVerify) == 0) {
setPasswordVerifyError(false);
@@ -113,7 +128,7 @@ const ConfigureValidatorKeys = () => {
setPassword(inputPassword);
setWithdrawalAddress(inputWithdrawalAddress);
history.push(usingExistingFlow ? CreateKeysExistingPath : CreateKeysCreatePath);
history.push(usingExistingFlow ? paths.CREATE_KEYS_EXISTING : paths.CREATE_KEYS_CREATE);
} else {
setPasswordVerifyError(true);
}

View File

@@ -1,12 +1,17 @@
import { Button, TextField, Tooltip, Typography } from "@mui/material";
import WizardWrapper from "../components/WizardWrapper";
import { BTECFlow, BTECImportPath, CreateCredentialsPath, errors, tooltips } from "../constants";
import { useContext, useEffect, useState } from "react";
import { useHistory } from "react-router-dom";
import { BTECContext } from "../BTECContext";
import { GlobalContext } from "../GlobalContext";
import Loader from "../components/Loader";
import { BTECContext } from "../BTECContext";
import Loader from "../components/Loader";
import WizardWrapper from "../components/WizardWrapper";
import { BTECFlow, errors, paths, tooltips } from "../constants";
import { GlobalContext } from "../GlobalContext";
/**
* Form to provide start index, validator indices, current withdrawal credentials, and the
* desired withdrawal address to be set.
*/
const ConfigureWithdrawalAddress = () => {
const {
btecCredentials,
@@ -35,7 +40,7 @@ const ConfigureWithdrawalAddress = () => {
useEffect(() => {
if (!mnemonic) {
history.replace(BTECImportPath);
history.replace(paths.BTEC_IMPORT);
}
}, []);
@@ -56,6 +61,14 @@ const ConfigureWithdrawalAddress = () => {
setInputWithdrawalAddress(e.target.value.trim());
};
/**
* Three step function:
* - Validate all inputs and make sure there are no errors
* - If no errors, validate the provided BLS credentials
* - If successful, send user to the creation step
*
* Any errors the user will be provided a corresponding error message
*/
const validateInputs = async () => {
let isError = false;
@@ -139,7 +152,7 @@ const ConfigureWithdrawalAddress = () => {
setIndex(inputIndex);
setWithdrawalAddress(inputWithdrawalAddress);
history.push(CreateCredentialsPath);
history.push(paths.CREATE_CREDENTIALS);
}).catch(() => {
setValidatingCredentials(false);
setCredentialsError(errors.BLS_CREDENTIALS_NO_MATCH);

View File

@@ -1,13 +1,19 @@
import { Button, Typography } from "@mui/material";
import WizardWrapper from "../components/WizardWrapper";
import { BTECFlow, BTECImportPath, FinishCredentialsPath } from "../constants";
import { useContext, useEffect, useState } from "react";
import { GlobalContext } from "../GlobalContext";
import { BTECContext } from "../BTECContext";
import Loader from "../components/Loader";
import FolderSelector from "../components/FolderSelector";
import { useHistory } from "react-router-dom";
import { BTECContext } from "../BTECContext";
import FolderSelector from "../components/FolderSelector";
import Loader from "../components/Loader";
import WizardWrapper from "../components/WizardWrapper";
import { BTECFlow, paths } from "../constants";
import { GlobalContext } from "../GlobalContext";
/**
* Allows the user to select a folder where the credentials will be saved
* and after which will attempt to generate the credential change and save
* to the specified folder
*/
const CreateCredentialsChange = () => {
const { network } = useContext(GlobalContext);
const {
@@ -26,7 +32,7 @@ const CreateCredentialsChange = () => {
useEffect(() => {
if (!mnemonic) {
history.replace(BTECImportPath);
history.replace(paths.BTEC_IMPORT);
}
}, []);
@@ -34,6 +40,10 @@ const CreateCredentialsChange = () => {
setSelectedFolder(folder);
};
/**
* Attempts to generate the credentials change and if successful send the user
* to the final step of the flow
*/
const handleBTECFileGeneration = () => {
setCreatingCredentialsChange(true);
let appendedWithdrawalAddress = withdrawalAddress;
@@ -52,7 +62,7 @@ const CreateCredentialsChange = () => {
appendedWithdrawalAddress,
).then(() => {
setFolderLocation(selectedFolder);
history.push(FinishCredentialsPath);
history.push(paths.FINISH_CREDENTIALS);
}).catch((error) => {
const errorMsg = ('stderr' in error) ? error.stderr : error.message;
setGenerationError(errorMsg);

View File

@@ -1,15 +1,20 @@
import { Button, Grid, IconButton, TextField, Tooltip, Typography } from "@mui/material";
import WizardWrapper from "../components/WizardWrapper";
import { StepKey } from "../types";
import Loader from "../components/Loader";
import { useContext, useEffect, useMemo, useState } from "react";
import { FileCopy } from "@mui/icons-material";
import VerifyMnemonic from "../components/VerifyMnemonic";
import { cleanMnemonic } from '../helpers';
import { Button, Grid, IconButton, TextField, Tooltip, Typography } from "@mui/material";
import { useContext, useEffect, useMemo, useState } from "react";
import { useHistory } from "react-router-dom";
import { ConfigureCreatePath } from "../constants";
import { KeyCreationContext } from "../KeyCreationContext";
import Loader from "../components/Loader";
import VerifyMnemonic from "../components/VerifyMnemonic";
import WizardWrapper from "../components/WizardWrapper";
import { paths } from "../constants";
import { cleanMnemonic } from '../helpers';
import { KeyCreationContext } from "../KeyCreationContext";
import { StepKey } from "../types";
/**
* Creates a new mnemonic for the user which will then be validated to make sure the
* user has stored it properly.
*/
const CreateMnemonic = () => {
const {mnemonic, setMnemonic} = useContext(KeyCreationContext);
const history = useHistory();
@@ -76,6 +81,9 @@ const CreateMnemonic = () => {
}
};
/**
* Creates an array of inputs that will display each word of the mnemonic
*/
const createMnemonicDisplay = () => {
return(
<Grid container item xs={10} spacing={2}>
@@ -124,7 +132,7 @@ const CreateMnemonic = () => {
if (cleanedMnemonic.localeCompare(cleanedMnemonicToVerify) === 0) {
setMnemonicValidationError(false);
history.push(ConfigureCreatePath);
history.push(paths.CONFIGURE_CREATE);
} else {
setMnemonicValidationError(true);
}

View File

@@ -1,13 +1,18 @@
import { useContext, useEffect, useState } from "react";
import { useHistory } from "react-router-dom";
import WizardWrapper from "../components/WizardWrapper";
import { Button, Typography } from "@mui/material";
import { CreateKeysExistingPath, CreateMnemonicFlow, CreatePath, ExistingImportPath, ExistingMnemonicFlow, FinishCreatePath, FinishExistingPath } from "../constants";
import { KeyCreationContext } from "../KeyCreationContext";
import FolderSelector from "../components/FolderSelector";
import Loader from "../components/Loader";
import WizardWrapper from "../components/WizardWrapper";
import { CreateMnemonicFlow, ExistingMnemonicFlow, paths } from "../constants";
import { GlobalContext } from "../GlobalContext";
import { KeyCreationContext } from "../KeyCreationContext";
/**
* Allows the user to select a destination folder for the validator keys.
* After which the user can attempt to generate the keys.
*/
const CreateValidatorKeys = () => {
const {
setFolderLocation,
@@ -19,7 +24,7 @@ const CreateValidatorKeys = () => {
} = useContext(KeyCreationContext);
const { network } = useContext(GlobalContext);
const history = useHistory();
const usingExistingFlow = history.location.pathname === CreateKeysExistingPath;
const usingExistingFlow = history.location.pathname === paths.CREATE_KEYS_EXISTING;
const [creatingKeys, setCreatingKeys] = useState(false);
const [generationError, setGenerationError] = useState("");
@@ -27,7 +32,7 @@ const CreateValidatorKeys = () => {
useEffect(() => {
if (!mnemonic) {
history.replace(usingExistingFlow ? ExistingImportPath : CreatePath);
history.replace(usingExistingFlow ? paths.EXISTING_IMPORT : paths.CREATE_MNEMONIC);
}
}, []);
@@ -35,6 +40,10 @@ const CreateValidatorKeys = () => {
setSelectedFolder(folder);
};
/**
* Will attempt to generate the validator keys with the provided folder and if successful
* will send the user to the final step of the flow
*/
const createKeys = () => {
setCreatingKeys(true);
@@ -54,7 +63,7 @@ const CreateValidatorKeys = () => {
selectedFolder,
).then(() => {
setFolderLocation(selectedFolder);
history.push(usingExistingFlow ? FinishExistingPath : FinishCreatePath);
history.push(usingExistingFlow ? paths.FINISH_EXISTING : paths.FINISH_CREATE);
}).catch((error) => {
const errorMsg = ('stderr' in error) ? error.stderr : error.message;
setGenerationError(errorMsg);

View File

@@ -1,10 +1,16 @@
import { Button, Link, Typography } from "@mui/material";
import { useContext, useEffect } from "react";
import { useHistory } from "react-router-dom";
import { BTECContext } from "../BTECContext";
import WizardWrapper from "../components/WizardWrapper";
import { BTECFlow } from "../constants";
import { useHistory } from "react-router-dom";
import { useContext, useEffect } from "react";
import { BTECContext } from "../BTECContext";
/**
* Final step of the credentials generation flow.
* Shows the user the location where the files were stored and provides
* some additional information.
*/
const FinishCredentialsGeneration = () => {
const { folderLocation } = useContext(BTECContext);
const history = useHistory();
@@ -15,6 +21,9 @@ const FinishCredentialsGeneration = () => {
}
}, []);
/**
* Will open a directory explorer where the credential change file was saved
*/
const openKeyLocation = () => {
window.bashUtils.findFirstFile(folderLocation, "keystore")
.then((keystoreFile) => {

View File

@@ -1,14 +1,19 @@
import { useHistory } from "react-router-dom";
import { CreateMnemonicFlow, ExistingMnemonicFlow, FinishExistingPath } from "../constants";
import { useContext, useEffect } from "react";
import { KeyCreationContext } from "../KeyCreationContext";
import WizardWrapper from "../components/WizardWrapper";
import { Button, Link, Typography } from "@mui/material";
import { useContext, useEffect } from "react";
import { useHistory } from "react-router-dom";
import WizardWrapper from "../components/WizardWrapper";
import { CreateMnemonicFlow, ExistingMnemonicFlow, paths } from "../constants";
import { KeyCreationContext } from "../KeyCreationContext";
/**
* Final step of creating validator keys. Will show the folder of where
* the keys were created and additional information
*/
const FinishKeyGeneration = () => {
const { folderLocation } = useContext(KeyCreationContext);
const history = useHistory();
const usingExistingFlow = history.location.pathname === FinishExistingPath;
const usingExistingFlow = history.location.pathname === paths.FINISH_EXISTING;
useEffect(() => {
if (!folderLocation) {
@@ -16,6 +21,9 @@ const FinishKeyGeneration = () => {
}
}, []);
/**
* Will open a directory explorer where the validator keys were saved
*/
const openKeyLocation = () => {
window.bashUtils.findFirstFile(folderLocation, "keystore")
.then((keystoreFile) => {
@@ -38,7 +46,7 @@ const FinishKeyGeneration = () => {
timelineItems={usingExistingFlow ? ExistingMnemonicFlow : CreateMnemonicFlow}
title="Create Keys"
>
<div className="tw-flex tw-flex-col tw-gap-2 tw-ml-28">
<div className="tw-flex tw-flex-col tw-gap-2 tw-mx-28">
<Typography variant="body1">
Your keys have been created here:{" "}
<Link

View File

@@ -1,13 +1,19 @@
import { Button, Tooltip, Typography } from "@mui/material";
import { useContext, useMemo, useState } from "react";
import { useHistory } from "react-router-dom";
import { paths, tooltips } from "../constants";
import { GlobalContext } from "../GlobalContext";
import { Button, Tooltip, Typography } from "@mui/material";
import NetworkPickerModal from "../modals/NetworkPickerModal";
import { KeyIcon } from "../icons/KeyIcon";
import { BTECImportPath, CreatePath, ExistingImportPath, tooltips } from "../constants";
import NetworkPickerModal from "../modals/NetworkPickerModal";
import ReuseMnemonicActionModal from "../modals/ReuseMnemonicActionModal";
import { ReuseMnemonicAction } from "../types";
/**
* Landed page of the application.
* The user will be able to select a network and choose the primary action
* they wish to make.
*/
const Home = () => {
const { network } = useContext(GlobalContext);
const [wasNetworkModalOpened, setWasNetworkModalOpened] = useState(false);
@@ -40,7 +46,7 @@ const Home = () => {
if (!wasNetworkModalOpened) {
handleOpenNetworkModal();
} else {
history.push(CreatePath)
history.push(paths.CREATE_MNEMONIC)
}
};
@@ -62,10 +68,10 @@ const Home = () => {
setShowReuseMnemonicModal(false);
if (action === ReuseMnemonicAction.RegenerateKeys) {
history.push(ExistingImportPath);
history.push(paths.EXISTING_IMPORT);
} else if (action === ReuseMnemonicAction.GenerateBLSToExecutionChange) {
history.push(BTECImportPath);
history.push(paths.BTEC_IMPORT);
}
};

View File

@@ -1,23 +1,38 @@
import { Button, TextField } from "@mui/material";
import { useHistory } from "react-router-dom";
import WizardWrapper from "../components/WizardWrapper";
import { useContext, useState } from "react";
import { cleanMnemonic } from "../helpers";
import { BTECFlow, BTECImportPath, ConfigureBTECPath, ConfigureExistingPath, ExistingMnemonicFlow, MNEMONIC_ERROR_SEARCH, VALID_MNEMONIC_LENGTHS, errors } from "../constants";
import Loader from "../components/Loader";
import { KeyCreationContext } from "../KeyCreationContext";
import { BTECContext } from "../BTECContext";
import { useHistory } from "react-router-dom";
import { BTECContext } from "../BTECContext";
import Loader from "../components/Loader";
import WizardWrapper from "../components/WizardWrapper";
import {
BTECFlow,
ExistingMnemonicFlow,
MNEMONIC_ERROR_SEARCH,
VALID_MNEMONIC_LENGTHS,
errors,
paths,
} from "../constants";
import { cleanMnemonic } from "../helpers";
import { KeyCreationContext } from "../KeyCreationContext";
/**
* Allows the user to import an existing mnemonic to kickstart either the
* validator key creation or withdrawal credentials change flow
*/
const MnemonicImport = () => {
const {mnemonic: btecMnemonic, setMnemonic: setBTECMnemonic} = useContext(BTECContext);
const {mnemonic, setMnemonic} = useContext(KeyCreationContext);
const history = useHistory();
const usingBTEC = history.location.pathname === BTECImportPath;
const usingBTEC = history.location.pathname === paths.BTEC_IMPORT;
const [error, setError] = useState("");
const [inputMnemonic, setInputMnemonic] = useState(usingBTEC ? btecMnemonic : mnemonic);
const [validatingMnemonic, setValidatingMnemonic] = useState(false);
/**
* Verifies the mnemonic is valid and will notify the user if not
*/
const verifyMnemonic = () => {
setError("");
@@ -37,7 +52,7 @@ const MnemonicImport = () => {
setMnemonic(cleanedMnemonic);
}
setValidatingMnemonic(false);
history.push(usingBTEC ? ConfigureBTECPath : ConfigureExistingPath);
history.push(usingBTEC ? paths.CONFIGURE_BTEC : paths.CONFIGURE_EXISTING);
}).catch((error) => {
const errorMsg = ('stderr' in error) ? error.stderr : error.message;

View File

@@ -34,6 +34,9 @@ module.exports = {
orange: "#F2994A",
},
extend: {
fontSize: {
xxs: ".625rem",
},
keyframes: {
OnlinePulse: {
"0%": {