Skip to content

Commit

Permalink
Feat: vol exit update (#220)
Browse files Browse the repository at this point in the history
  • Loading branch information
rickimoore authored Sep 22, 2023
1 parent 23ba577 commit 4be1e88
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 34 deletions.
44 changes: 10 additions & 34 deletions src/components/ValidatorModal/views/ValidatorExit.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ValidatorInfoHeader from '../../ValidatorInfoHeader/ValidatorInfoHeader'
import { SignedExitData, ValidatorInfo } from '../../../types/validator'
import { ValidatorInfo } from '../../../types/validator'
import { FC, useContext, useState } from 'react'
import Typography from '../../Typography/Typography'
import { ValidatorModalContext } from '../ValidatorModal'
Expand All @@ -9,13 +9,10 @@ import InfoBox, { InfoBoxType } from '../../InfoBox/InfoBox'
import Button, { ButtonFace } from '../../Button/Button'
import { Trans, useTranslation } from 'react-i18next'
import addClassString from '../../../utilities/addClassString'
import { signVoluntaryExit } from '../../../api/lighthouse'
import { useRecoilValue } from 'recoil'
import { submitSignedExit } from '../../../api/beacon'
import ExitDisclosure from '../../Disclosures/ExitDisclosure'
import displayToast from '../../../utilities/displayToast'
import { activeDevice } from '../../../recoil/atoms'
import { ToastType } from '../../../types'
import useExitValidator from '../../../hooks/useExitValidator'

export interface ValidatorExitProps {
validator: ValidatorInfo
Expand All @@ -24,48 +21,27 @@ export interface ValidatorExitProps {
const ValidatorExit: FC<ValidatorExitProps> = ({ validator }) => {
const { t } = useTranslation()
const { pubKey } = validator
const [isLoading, setLoading] = useState(false)
const { rawValidatorUrl, apiToken, beaconUrl } = useRecoilValue(activeDevice)
const [isAccept, setIsAccept] = useState(false)
const { moveToView, closeModal } = useContext(ValidatorModalContext)
const viewDetails = () => moveToView(ValidatorModalView.DETAILS)
const { isLoading, setLoading, getSignedExit, submitSignedMessage } = useExitValidator(
apiToken,
pubKey,
beaconUrl,
)

const acceptBtnClasses = addClassString('', [isAccept && 'border-success !text-success'])
const checkMarkClasses = addClassString('bi bi-check-circle ml-4', [isAccept && 'text-success'])

const getSignedExit = async (url: string): Promise<SignedExitData | undefined> => {
try {
const { data } = await signVoluntaryExit(url, apiToken, pubKey)

if (data) {
return data
}
} catch (e) {
setLoading(false)
displayToast(t('error.unableToSignExit'), ToastType.ERROR)
}
}
const submitSignedMessage = async (data: SignedExitData) => {
try {
const { status } = await submitSignedExit(beaconUrl, data)

if (status === 200) {
setLoading(false)
displayToast(t('success.validatorExit'), ToastType.SUCCESS)
closeModal()
}
} catch (e) {
setLoading(false)
displayToast(t('error.invalidExit'), ToastType.ERROR)
}
}

const confirmExit = async () => {
setLoading(true)

const message = await getSignedExit(rawValidatorUrl)

if (message) {
void (await submitSignedMessage(message))
await submitSignedMessage(message)
closeModal()
}
}
const toggleAccept = () => setIsAccept((prev) => !prev)
Expand Down
82 changes: 82 additions & 0 deletions src/hooks/__tests__/useExitValidator.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { renderHook, act } from '@testing-library/react-hooks'
import useExitValidator from '../useExitValidator'
import { signVoluntaryExit } from '../../api/lighthouse'
import { submitSignedExit } from '../../api/beacon'
import displayToast from '../../utilities/displayToast'

jest.mock('../../api/lighthouse')
jest.mock('../../api/beacon')
jest.mock('../../utilities/displayToast')
jest.mock('react-i18next')

const mockedSignVoluntaryExit = signVoluntaryExit as jest.MockedFn<typeof signVoluntaryExit>
const mockedDisplayToast = displayToast as jest.MockedFn<typeof displayToast>
const mockedSubmitExit = submitSignedExit as jest.MockedFn<typeof submitSignedExit>

const mockExitData = {
message: {
epoch: 'mock-epoch',
validator_index: '0',
},
signature: 'mock-signature',
}

const mockResponse = {
data: undefined,
status: 200,
statusText: 'OK',
headers: {},
config: {},
}

const setup = async (signingResponse: any, submissionResponse: any) => {
mockedSignVoluntaryExit.mockResolvedValue(signingResponse)
mockedSubmitExit.mockResolvedValue(submissionResponse)
const { result } = renderHook(() => useExitValidator('testToken', 'testPubKey', 'testUrl'))

let signedExitData
await act(async () => {
signedExitData = await result.current.getSignedExit('testUrl')
if (signedExitData) {
await result.current.submitSignedMessage(signedExitData)
}
})

return { result, signedExitData }
}

describe('useExitValidator', () => {
beforeEach(() => {
mockedSignVoluntaryExit.mockClear()
mockedSubmitExit.mockClear()
mockedDisplayToast.mockClear()
})

it('should handle successful signing and submission when returned data.data', async () => {
const { signedExitData } = await setup(
{ ...mockResponse, data: { data: mockExitData } },
mockResponse,
)
expect(signedExitData).toEqual(mockExitData)
expect(displayToast).toHaveBeenCalledWith('success.validatorExit', 'success')
})

it('should handle successful signing and submission when returned data', async () => {
const { signedExitData } = await setup({ ...mockResponse, data: mockExitData }, mockResponse)
expect(signedExitData).toEqual(mockExitData)
expect(displayToast).toHaveBeenCalledWith('success.validatorExit', 'success')
})

it('should handle error during signing', async () => {
await setup(Promise.reject(new Error('Error during signing')), mockResponse)
expect(displayToast).toHaveBeenCalledWith('error.unableToSignExit', 'error')
})

it('should handle error during submission', async () => {
await setup(
{ ...mockResponse, data: mockExitData },
Promise.reject(new Error('Error during submission')),
)
expect(displayToast).toHaveBeenCalledWith('error.invalidExit', 'error')
})
})
47 changes: 47 additions & 0 deletions src/hooks/useExitValidator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { SignedExitData } from '../types/validator'
import { signVoluntaryExit } from '../api/lighthouse'
import displayToast from '../utilities/displayToast'
import { ToastType } from '../types'
import { submitSignedExit } from '../api/beacon'
import { useTranslation } from 'react-i18next'
import { useState } from 'react'

const useExitValidator = (apiToken: string, pubKey: string, beaconUrl: string) => {
const { t } = useTranslation()
const [isLoading, setLoading] = useState(false)

const getSignedExit = async (url: string): Promise<SignedExitData | undefined> => {
try {
const { data } = await signVoluntaryExit(url, apiToken, pubKey)

if (data) {
return data?.data || data
}
} catch (e) {
setLoading(false)
displayToast(t('error.unableToSignExit'), ToastType.ERROR)
}
}
const submitSignedMessage = async (data: SignedExitData) => {
try {
const { status } = await submitSignedExit(beaconUrl, data)

if (status === 200) {
setLoading(false)
displayToast(t('success.validatorExit'), ToastType.SUCCESS)
}
} catch (e) {
setLoading(false)
displayToast(t('error.invalidExit'), ToastType.ERROR)
}
}

return {
isLoading,
setLoading,
getSignedExit,
submitSignedMessage,
}
}

export default useExitValidator

0 comments on commit 4be1e88

Please sign in to comment.