From b61d5b7324d50a5b42a0faa172235fd7fe61ab6f Mon Sep 17 00:00:00 2001 From: Mavrik Date: Wed, 2 Jul 2025 14:25:39 +0200 Subject: [PATCH] wip --- app/dashboard/validators/Main.tsx | 5 ++ backend/src/beacon/beacon.service.ts | 2 +- .../AddValidatorView/AddValidatorView.tsx | 1 - .../Steps/MnemonicPhrase.tsx | 2 +- .../ImportValidatorView.tsx | 25 ++++++ .../CreateKeystoreView/CreateKeystoreView.tsx | 83 +++++++++++++++++++ .../steps/ConfirmImport.tsx | 33 ++++++++ .../steps/ImportSuggestedFee.tsx | 34 ++++++++ .../steps/ImportedKeystoreAuthentication.tsx | 58 +++++++++++++ .../steps/ImportedMnemonicIndex.tsx | 55 ++++++++++++ .../views/SelectImportTypeView.tsx | 58 +++++++++++++ .../views/UploadKeystoreView.tsx | 5 ++ src/constants/enums.ts | 6 ++ src/hooks/usePasswordConfirmation.ts | 42 ++++++++++ 14 files changed, 406 insertions(+), 3 deletions(-) create mode 100644 src/components/ValidatorManagement/ImportValidatorView/ImportValidatorView.tsx create mode 100644 src/components/ValidatorManagement/ImportValidatorView/views/CreateKeystoreView/CreateKeystoreView.tsx create mode 100644 src/components/ValidatorManagement/ImportValidatorView/views/CreateKeystoreView/steps/ConfirmImport.tsx create mode 100644 src/components/ValidatorManagement/ImportValidatorView/views/CreateKeystoreView/steps/ImportSuggestedFee.tsx create mode 100644 src/components/ValidatorManagement/ImportValidatorView/views/CreateKeystoreView/steps/ImportedKeystoreAuthentication.tsx create mode 100644 src/components/ValidatorManagement/ImportValidatorView/views/CreateKeystoreView/steps/ImportedMnemonicIndex.tsx create mode 100644 src/components/ValidatorManagement/ImportValidatorView/views/SelectImportTypeView.tsx create mode 100644 src/components/ValidatorManagement/ImportValidatorView/views/UploadKeystoreView.tsx create mode 100644 src/hooks/usePasswordConfirmation.ts diff --git a/app/dashboard/validators/Main.tsx b/app/dashboard/validators/Main.tsx index f7e05978..76fafea7 100644 --- a/app/dashboard/validators/Main.tsx +++ b/app/dashboard/validators/Main.tsx @@ -13,6 +13,7 @@ import Typography from '../../../src/components/Typography/Typography' import AddValidatorView from '../../../src/components/ValidatorManagement/AddValidatorView/AddValidatorView' import ConsolidateView from '../../../src/components/ValidatorManagement/ConsolidateView/ConsolidateView' import CreateValidatorView from '../../../src/components/ValidatorManagement/CreateValidatorView/CreateValidatorView' +import ImportValidatorView from '../../../src/components/ValidatorManagement/ImportValidatorView/ImportValidatorView' import MainView from '../../../src/components/ValidatorManagement/MainView' import ValidatorModal from '../../../src/components/ValidatorModal/ValidatorModal' import ValidatorSummary from '../../../src/components/ValidatorSummary/ValidatorSummary' @@ -288,6 +289,8 @@ const Main: FC = (props) => { return t('validatorManagement.titles.add') case ValidatorManagementView.CONSOLIDATE: return 'Consolidate' + case ValidatorManagementView.IMPORT: + return 'Import Validator' default: return t('validatorManagement.titles.main') } @@ -310,6 +313,8 @@ const Main: FC = (props) => { validators={validatorStates} /> ) + case ValidatorManagementView.IMPORT: + return default: return ( = ({ onChangeView }) => { title: t('validatorManagement.addValidator.options.import.title'), subTitle: t('validatorManagement.addValidator.options.import.subTitle'), caption: t('validatorManagement.addValidator.options.import.caption'), - isDisabled: true, isRecommended: false, SVG: BlockChainSvg, view: ValidatorManagementView.IMPORT, diff --git a/src/components/ValidatorManagement/CreateValidatorView/Steps/MnemonicPhrase.tsx b/src/components/ValidatorManagement/CreateValidatorView/Steps/MnemonicPhrase.tsx index 6d8f08f7..11a40107 100644 --- a/src/components/ValidatorManagement/CreateValidatorView/Steps/MnemonicPhrase.tsx +++ b/src/components/ValidatorManagement/CreateValidatorView/Steps/MnemonicPhrase.tsx @@ -16,7 +16,7 @@ import StepOptions from '../StepOptions' export interface MnemonicPhraseProps extends InputHTMLAttributes { onNextStep: () => void - onBackStep: () => void + onBackStep?: () => void isActive: boolean blsModule: IBls } diff --git a/src/components/ValidatorManagement/ImportValidatorView/ImportValidatorView.tsx b/src/components/ValidatorManagement/ImportValidatorView/ImportValidatorView.tsx new file mode 100644 index 00000000..baca7ef7 --- /dev/null +++ b/src/components/ValidatorManagement/ImportValidatorView/ImportValidatorView.tsx @@ -0,0 +1,25 @@ +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { ImportView } from '../../../constants/enums' +import CreateKeystoreView from './views/CreateKeystoreView/CreateKeystoreView' +import SelectImportTypeView from './views/SelectImportTypeView' +import UploadKeystoreView from './views/UploadKeystoreView' + +const ImportValidatorView = () => { + const { t } = useTranslation() + const [view, setView] = useState(ImportView.SELECT) + + const moveToView = (nextView: ImportView) => setView(nextView) + + if (view === ImportView.CREATE) { + return + } + + if (view === ImportView.UPLOAD) { + return + } + + return +} + +export default ImportValidatorView diff --git a/src/components/ValidatorManagement/ImportValidatorView/views/CreateKeystoreView/CreateKeystoreView.tsx b/src/components/ValidatorManagement/ImportValidatorView/views/CreateKeystoreView/CreateKeystoreView.tsx new file mode 100644 index 00000000..0df252c5 --- /dev/null +++ b/src/components/ValidatorManagement/ImportValidatorView/views/CreateKeystoreView/CreateKeystoreView.tsx @@ -0,0 +1,83 @@ +import { ChangeEvent, useCallback, useState } from 'react' +import { useRecoilValue } from 'recoil' +import { blsModuleAtom } from '../../../../../recoil/atoms' +import HorizontalStepper from '../../../../HorizontalStepper/HorizontalStepper' +import Spinner from '../../../../Spinner/Spinner' +import MnemonicPhrase from '../../../CreateValidatorView/Steps/MnemonicPhrase' +import ConfirmImport from './steps/ConfirmImport' +import ImportedKeystoreAuthentication from './steps/ImportedKeystoreAuthentication' +import ImportedMnemonicIndex from './steps/ImportedMnemonicIndex' +import ImportSuggestedFee from './steps/ImportSuggestedFee' + +const CreateKeystoreView = () => { + const blsModule = useRecoilValue(blsModuleAtom) + const [keyStoreAuth, setKeyStoreAuth] = useState('') + const [suggestedFee, setSuggestedFee] = useState('') + const [keyPhrase, setKeyPhrase] = useState('') + const [mnemonicIndex, setIndex] = useState('') + + const setCredential = (value: string | undefined) => setSuggestedFee(value) + const steps = ['Enter Mnemonic', 'Set Index', 'Set Authentication', 'Set Fee Recipient', 'Upload'] + + const setPhrase = useCallback((e: ChangeEvent) => { + setKeyPhrase(e.target.value) + }, []) + + const onIndexChange = (index: string | undefined) => { + setIndex(index) + } + + const onStoreKeyStoreAuth = (value: string | undefined) => setKeyStoreAuth(value) + + return blsModule ? ( + + {({ incrementStep, decrementStep, step }) => + ( + <> +
+ +
+ + + + {!!mnemonicIndex && !!keyPhrase ? ( + + ) : null} + + ) as any + } +
+ ) : ( +
+ +
+ ) +} + +export default CreateKeystoreView diff --git a/src/components/ValidatorManagement/ImportValidatorView/views/CreateKeystoreView/steps/ConfirmImport.tsx b/src/components/ValidatorManagement/ImportValidatorView/views/CreateKeystoreView/steps/ConfirmImport.tsx new file mode 100644 index 00000000..57da23ba --- /dev/null +++ b/src/components/ValidatorManagement/ImportValidatorView/views/CreateKeystoreView/steps/ConfirmImport.tsx @@ -0,0 +1,33 @@ +import { IBls } from '@chainsafe/bls/types' +import { FC, useMemo } from 'react' +import useChainSafeKeygen from '../../../../../../hooks/useChainSafeKeygen' + +export interface ConfirmImportProps { + keyPhrase: string + index: string + password: string | undefined + suggestedFeeRecipient: string | undefined + blsModule: IBls +} + +const ConfirmImport: FC = ({ + keyPhrase, + index, + suggestedFeeRecipient, + password, + blsModule, +}) => { + const { deriveEIP2334SubKey, generateSigningPubKey } = useChainSafeKeygen(blsModule) + + const eip2334SubKey = useMemo(() => { + return deriveEIP2334SubKey(keyPhrase) + }, [keyPhrase]) + + const pubKey = useMemo(() => { + return generateSigningPubKey(eip2334SubKey, Number(index)) + }, [index]) + + return
{pubKey}
+} + +export default ConfirmImport diff --git a/src/components/ValidatorManagement/ImportValidatorView/views/CreateKeystoreView/steps/ImportSuggestedFee.tsx b/src/components/ValidatorManagement/ImportValidatorView/views/CreateKeystoreView/steps/ImportSuggestedFee.tsx new file mode 100644 index 00000000..0e056761 --- /dev/null +++ b/src/components/ValidatorManagement/ImportValidatorView/views/CreateKeystoreView/steps/ImportSuggestedFee.tsx @@ -0,0 +1,34 @@ +import { FC, useState } from 'react' +import CredentialInput from '../../../../../ValidatorCredentialRow/CredentialInput' +import StepOptions, { StepOptionsProps } from '../../../../CreateValidatorView/StepOptions' + +export interface ImportSuggestedFeeProps extends Omit { + address: string | undefined + onSetAddress: (value: string | undefined) => void +} + +const ImportSuggestedFee: FC = ({ + address, + onSetAddress, + onBackStep, + onNextStep, +}) => { + const [isVerified, setIsVerified] = useState(false) + const verifyWithdrawalCredential = (value: boolean) => setIsVerified(value) + + return ( +
+ + +
+ ) +} + +export default ImportSuggestedFee diff --git a/src/components/ValidatorManagement/ImportValidatorView/views/CreateKeystoreView/steps/ImportedKeystoreAuthentication.tsx b/src/components/ValidatorManagement/ImportValidatorView/views/CreateKeystoreView/steps/ImportedKeystoreAuthentication.tsx new file mode 100644 index 00000000..042191d7 --- /dev/null +++ b/src/components/ValidatorManagement/ImportValidatorView/views/CreateKeystoreView/steps/ImportedKeystoreAuthentication.tsx @@ -0,0 +1,58 @@ +import { FC } from 'react' +import { useTranslation } from 'react-i18next' +import usePasswordConfirmation from '../../../../../../hooks/usePasswordConfirmation' +import Input from '../../../../../Input/Input' +import StepOptions, { StepOptionsProps } from '../../../../CreateValidatorView/StepOptions' + +export interface ImportedKeystoreAuthenticationProps + extends Omit { + onStoreAuth: (value: string) => void +} + +const ImportedKeystoreAuthentication: FC = ({ + onBackStep, + onNextStep, + onStoreAuth, +}) => { + const { t } = useTranslation() + const { password, error, confirmationError, isValid, storeConfirmationPassword, storePassword } = + usePasswordConfirmation() + const moveToNextStep = () => { + onStoreAuth(password) + onNextStep?.() + } + + return ( +
+
+ {isValid && ( + + )} + +
+
+ {isValid && ( + + )} + +
+ +
+ ) +} + +export default ImportedKeystoreAuthentication diff --git a/src/components/ValidatorManagement/ImportValidatorView/views/CreateKeystoreView/steps/ImportedMnemonicIndex.tsx b/src/components/ValidatorManagement/ImportValidatorView/views/CreateKeystoreView/steps/ImportedMnemonicIndex.tsx new file mode 100644 index 00000000..bcdd20ab --- /dev/null +++ b/src/components/ValidatorManagement/ImportValidatorView/views/CreateKeystoreView/steps/ImportedMnemonicIndex.tsx @@ -0,0 +1,55 @@ +import { ChangeEvent, FC } from 'react' +import { useTranslation } from 'react-i18next' +import { MAX_MNEMONIC_INDEX } from '../../../../../../constants/constants' +import Typography from '../../../../../Typography/Typography' +import StepOptions from '../../../../CreateValidatorView/StepOptions' + +export interface ImportedMnemonicIndexProps { + mnemonicIndex: string | undefined + onSetIndex: (index: string | undefined) => void + onNextStep: () => void + onBackStep: () => void +} + +const ImportedMnemonicIndex: FC = ({ + mnemonicIndex, + onSetIndex, + onBackStep, + onNextStep, +}) => { + const { t } = useTranslation() + + const handleIndexChange = (event: ChangeEvent) => { + onSetIndex(event.target.value) + } + + return ( +
+
+ + {t('validatorManagement.mnemonicIndexing.title')} -- + + + {t('validatorManagement.mnemonicIndexing.subTitle')} + +
+
+ +
+ +
+ ) +} + +export default ImportedMnemonicIndex diff --git a/src/components/ValidatorManagement/ImportValidatorView/views/SelectImportTypeView.tsx b/src/components/ValidatorManagement/ImportValidatorView/views/SelectImportTypeView.tsx new file mode 100644 index 00000000..a203d6cb --- /dev/null +++ b/src/components/ValidatorManagement/ImportValidatorView/views/SelectImportTypeView.tsx @@ -0,0 +1,58 @@ +import { FC } from 'react' +import { useTranslation } from 'react-i18next' +import { ImportView } from '../../../../constants/enums' +import Typography from '../../../Typography/Typography' + +export interface SelectImportTypeViewProps { + onChangeView: (view: ImportView) => void +} + +const SelectImportTypeView: FC = ({ onChangeView }) => { + const { t } = useTranslation() + + const viewKeystoreUpload = () => onChangeView(ImportView.UPLOAD) + const viewCreateKeystore = () => onChangeView(ImportView.CREATE) + + return ( +
+
+ {t('validatorManagement.addValidator.title')} + + {t('validatorManagement.addValidator.subTitle')} + +
+
+ + +
+
+ ) +} + +export default SelectImportTypeView diff --git a/src/components/ValidatorManagement/ImportValidatorView/views/UploadKeystoreView.tsx b/src/components/ValidatorManagement/ImportValidatorView/views/UploadKeystoreView.tsx new file mode 100644 index 00000000..813d7d58 --- /dev/null +++ b/src/components/ValidatorManagement/ImportValidatorView/views/UploadKeystoreView.tsx @@ -0,0 +1,5 @@ +const UploadKeystoreView = () => { + return
upload keystore
+} + +export default UploadKeystoreView diff --git a/src/constants/enums.ts b/src/constants/enums.ts index 7012b876..a53961b3 100644 --- a/src/constants/enums.ts +++ b/src/constants/enums.ts @@ -85,3 +85,9 @@ export enum SettingsView { GENERAL = 'GENERAL', ABOUT = 'ABOUT', } + +export enum ImportView { + SELECT = 'SELECT', + CREATE = 'CREATE', + UPLOAD = 'UPLOAD', +} diff --git a/src/hooks/usePasswordConfirmation.ts b/src/hooks/usePasswordConfirmation.ts new file mode 100644 index 00000000..6d0c5aa6 --- /dev/null +++ b/src/hooks/usePasswordConfirmation.ts @@ -0,0 +1,42 @@ +import { ChangeEvent, useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' + +const usePasswordConfirmation = () => { + const { t } = useTranslation() + + const [password, setPassword] = useState('') + const [confirmationPassword, setConfirmationPassword] = useState('') + const errorMessages = useMemo(() => { + const rules = [ + { test: /.{12,}/, error: t('error.length') }, + { test: /[a-z]/, error: t('error.lowercaseRequired') }, + { test: /[A-Z]/, error: t('error.uppercaseRequired') }, + { test: /[0-9]/, error: t('error.numberRequired') }, + { test: /[$&+,:;=?@#|'<>.^*()%!-]/, error: t('error.specialCharRequired') }, + ] + + return password + ? rules.filter((rule) => !rule.test.test(password)).map((rule) => rule.error) + : [] + }, [password]) + const isComplete = Boolean(password && confirmationPassword) + const isMatchingPassword = password === confirmationPassword + const isValid = isComplete && isMatchingPassword && !errorMessages.length + + const storePassword = (e: ChangeEvent) => setPassword(e.target.value) + const storeConfirmationPassword = (e: ChangeEvent) => + setConfirmationPassword(e.target.value) + + return { + password, + error: errorMessages.length ? errorMessages.join(' • ') : undefined, + confirmationError: !isMatchingPassword ? t('error.passwordMatch') : undefined, + isMatchingPassword, + confirmationPassword, + isValid, + storePassword, + storeConfirmationPassword, + } +} + +export default usePasswordConfirmation