Commit 57a1d38f authored by tom's avatar tom

refactor second step

parent a4276b6d
...@@ -197,7 +197,7 @@ const ContractCode = ({ addressHash }: Props) => { ...@@ -197,7 +197,7 @@ const ContractCode = ({ addressHash }: Props) => {
<RawDataSnippet <RawDataSnippet
data={ data.creation_bytecode } data={ data.creation_bytecode }
title="Contract creation code" title="Contract creation code"
rightSlot={ data.is_verified ? null : verificationButton } rightSlot={ data.is_verified || data.is_self_destructed ? null : verificationButton }
beforeSlot={ data.is_self_destructed ? ( beforeSlot={ data.is_self_destructed ? (
<Alert status="info" whiteSpace="pre-wrap" mb={ 3 }> <Alert status="info" whiteSpace="pre-wrap" mb={ 3 }>
Contracts that self destruct in their constructors have no contract code published and cannot be verified. Contracts that self destruct in their constructors have no contract code published and cannot be verified.
......
...@@ -24,6 +24,10 @@ const AddressVerificationModal = ({ isOpen, onClose }: Props) => { ...@@ -24,6 +24,10 @@ const AddressVerificationModal = ({ isOpen, onClose }: Props) => {
setStepIndex((prev) => prev + 1); setStepIndex((prev) => prev + 1);
}, []); }, []);
const handleGoToThirdStep = React.useCallback(() => {
setStepIndex((prev) => prev + 1);
}, []);
const handleGoToPrevStep = React.useCallback(() => { const handleGoToPrevStep = React.useCallback(() => {
setStepIndex((prev) => prev - 1); setStepIndex((prev) => prev - 1);
}, []); }, []);
...@@ -35,7 +39,7 @@ const AddressVerificationModal = ({ isOpen, onClose }: Props) => { ...@@ -35,7 +39,7 @@ const AddressVerificationModal = ({ isOpen, onClose }: Props) => {
const steps = [ const steps = [
{ title: 'Verify new address ownership', content: <AddressVerificationStepAddress onContinue={ handleGoToSecondStep }/> }, { title: 'Verify new address ownership', content: <AddressVerificationStepAddress onContinue={ handleGoToSecondStep }/> },
{ title: 'Sign message', content: <AddressVerificationStepSignature { ...data }/> }, { title: 'Copy and sign message', content: <AddressVerificationStepSignature { ...data } onContinue={ handleGoToThirdStep }/> },
{ title: 'Congrats! Address is verified.', content: <AddressVerificationStepSuccess onShowListClick={ handleClose } onAddTokenClick={ handleClose }/> }, { title: 'Congrats! Address is verified.', content: <AddressVerificationStepSuccess onShowListClick={ handleClose } onAddTokenClick={ handleClose }/> },
]; ];
const step = steps[stepIndex]; const step = steps[stepIndex];
......
...@@ -3,17 +3,19 @@ import React from 'react'; ...@@ -3,17 +3,19 @@ import React from 'react';
import type { Control, ControllerRenderProps, FormState } from 'react-hook-form'; import type { Control, ControllerRenderProps, FormState } from 'react-hook-form';
import { Controller } from 'react-hook-form'; import { Controller } from 'react-hook-form';
import type { AddressVerificationFormSecondStepFields } from '../types'; import type { AddressVerificationFormSecondStepFields, RootFields } from '../types';
import InputPlaceholder from 'ui/shared/InputPlaceholder'; import InputPlaceholder from 'ui/shared/InputPlaceholder';
type Fields = RootFields & AddressVerificationFormSecondStepFields;
interface Props { interface Props {
formState: FormState<AddressVerificationFormSecondStepFields>; formState: FormState<Fields>;
control: Control<AddressVerificationFormSecondStepFields>; control: Control<Fields>;
} }
const AddressVerificationFieldMessage = ({ formState, control }: Props) => { const AddressVerificationFieldMessage = ({ formState, control }: Props) => {
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<AddressVerificationFormSecondStepFields, 'message'>}) => { const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<Fields, 'message'>}) => {
const error = 'message' in formState.errors ? formState.errors.message : undefined; const error = 'message' in formState.errors ? formState.errors.message : undefined;
return ( return (
...@@ -22,14 +24,14 @@ const AddressVerificationFieldMessage = ({ formState, control }: Props) => { ...@@ -22,14 +24,14 @@ const AddressVerificationFieldMessage = ({ formState, control }: Props) => {
{ ...field } { ...field }
required required
isInvalid={ Boolean(error) } isInvalid={ Boolean(error) }
isDisabled={ formState.isSubmitting } isDisabled
autoComplete="off" autoComplete="off"
maxH="105px" maxH="105px"
/> />
<InputPlaceholder text="Message to sign" error={ error }/> <InputPlaceholder text="Message to sign" error={ error }/>
</FormControl> </FormControl>
); );
}, [ formState.errors, formState.isSubmitting ]); }, [ formState.errors ]);
return ( return (
<Controller <Controller
......
...@@ -3,19 +3,21 @@ import React from 'react'; ...@@ -3,19 +3,21 @@ import React from 'react';
import type { Control, ControllerRenderProps, FormState } from 'react-hook-form'; import type { Control, ControllerRenderProps, FormState } from 'react-hook-form';
import { Controller } from 'react-hook-form'; import { Controller } from 'react-hook-form';
import type { AddressVerificationFormSecondStepFields } from '../types'; import type { AddressVerificationFormSecondStepFields, RootFields } from '../types';
import { SIGNATURE_REGEXP } from 'lib/validations/signature'; import { SIGNATURE_REGEXP } from 'lib/validations/signature';
import InputPlaceholder from 'ui/shared/InputPlaceholder'; import InputPlaceholder from 'ui/shared/InputPlaceholder';
type Fields = RootFields & AddressVerificationFormSecondStepFields;
interface Props { interface Props {
formState: FormState<AddressVerificationFormSecondStepFields>; formState: FormState<Fields>;
control: Control<AddressVerificationFormSecondStepFields>; control: Control<Fields>;
} }
const AddressVerificationFieldSignature = ({ formState, control }: Props) => { const AddressVerificationFieldSignature = ({ formState, control }: Props) => {
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<AddressVerificationFormSecondStepFields, 'signature'>}) => { const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<Fields, 'signature'>}) => {
const error = 'signature' in formState.errors ? formState.errors.signature : undefined; const error = 'signature' in formState.errors ? formState.errors.signature : undefined;
return ( return (
......
...@@ -4,7 +4,7 @@ import type { SubmitHandler } from 'react-hook-form'; ...@@ -4,7 +4,7 @@ import type { SubmitHandler } from 'react-hook-form';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import type { import type {
AddressCheckResponseError, AddressVerificationResponseError,
AddressCheckResponseSuccess, AddressCheckResponseSuccess,
AddressCheckStatusSuccess, AddressCheckStatusSuccess,
AddressVerificationFormFirstStepFields, AddressVerificationFormFirstStepFields,
...@@ -12,6 +12,7 @@ import type { ...@@ -12,6 +12,7 @@ import type {
} from '../types'; } from '../types';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import type { ResourceError } from 'lib/api/resources';
import useApiFetch from 'lib/api/useApiFetch'; import useApiFetch from 'lib/api/useApiFetch';
import AddressVerificationFieldAddress from '../fields/AddressVerificationFieldAddress'; import AddressVerificationFieldAddress from '../fields/AddressVerificationFieldAddress';
...@@ -35,36 +36,42 @@ const AddressVerificationStepAddress = ({ onContinue }: Props) => { ...@@ -35,36 +36,42 @@ const AddressVerificationStepAddress = ({ onContinue }: Props) => {
}, [ address, clearErrors ]); }, [ address, clearErrors ]);
const onFormSubmit: SubmitHandler<Fields> = React.useCallback(async(data) => { const onFormSubmit: SubmitHandler<Fields> = React.useCallback(async(data) => {
const body = { try {
contractAddress: data.address, const body = {
}; contractAddress: data.address,
const response = await apiFetch<'address_verification', AddressCheckResponseSuccess, AddressCheckResponseError>('address_verification', { };
fetchParams: { method: 'POST', body }, const response = await apiFetch<'address_verification', AddressCheckResponseSuccess, AddressVerificationResponseError>('address_verification', {
pathParams: { chainId: appConfig.network.id, type: ':prepare' }, fetchParams: { method: 'POST', body },
}); pathParams: { chainId: appConfig.network.id, type: ':prepare' },
});
if (response.status !== 'SUCCESS') { if (response.status !== 'SUCCESS') {
switch (response.status) { switch (response.status) {
case 'INVALID_ADDRESS_ERROR': { case 'INVALID_ADDRESS_ERROR': {
return setError('root', { type: 'manual', message: 'Specified address either does not exist or is EOA' }); return setError('root', { type: 'manual', message: 'Specified address either does not exist or is EOA' });
} }
case 'IS_OWNER_ERROR': { case 'IS_OWNER_ERROR': {
return setError('root', { type: 'manual', message: 'User is already an owner of the address' }); return setError('root', { type: 'manual', message: 'User is already an owner of the address' });
} }
case 'OWNERSHIP_VERIFIED_ERROR': { case 'OWNERSHIP_VERIFIED_ERROR': {
return setError('root', { type: 'manual', message: 'Address ownership has been verified by another account' }); return setError('root', { type: 'manual', message: 'Address ownership has been verified by another account' });
} }
case 'SOURCE_CODE_NOT_VERIFIED_ERROR': { case 'SOURCE_CODE_NOT_VERIFIED_ERROR': {
return setError('root', { type: 'manual', message: 'Contract source code has not been verified' }); return setError('root', { type: 'manual', message: 'Contract source code has not been verified' });
} }
default: { default: {
return setError('root', { type: 'manual', message: response.payload?.message || 'Oops! Something went wrong' }); return setError('root', { type: 'manual', message: response.payload?.message || 'Oops! Something went wrong' });
}
} }
} }
onContinue({ ...response.result, address: data.address });
} catch (_error) {
const error = _error as ResourceError<AddressVerificationResponseError>;
setError('root', { type: 'manual', message: error.payload?.message || 'Oops! Something went wrong' });
} }
onContinue({ ...response.result, address: data.address });
}, [ apiFetch, onContinue, setError ]); }, [ apiFetch, onContinue, setError ]);
const onSubmit = handleSubmit(onFormSubmit); const onSubmit = handleSubmit(onFormSubmit);
......
import { Alert, Box, Button, Flex, Radio, RadioGroup } from '@chakra-ui/react'; import { Alert, Box, Button, chakra, Flex, Link, Radio, RadioGroup } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { SubmitHandler } from 'react-hook-form'; import type { SubmitHandler } from 'react-hook-form';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { useSignMessage } from 'wagmi'; import { useSignMessage } from 'wagmi';
import type { AddressVerificationFormSecondStepFields, AddressCheckStatusSuccess, AddressVerificationFormFirstStepFields } from '../types'; import type {
AddressVerificationFormSecondStepFields,
AddressCheckStatusSuccess,
AddressVerificationFormFirstStepFields,
RootFields,
AddressVerificationResponseError,
AddressValidationResponseSuccess,
} from '../types';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import type { ResourceError } from 'lib/api/resources'; import type { ResourceError } from 'lib/api/resources';
...@@ -13,35 +20,88 @@ import useApiFetch from 'lib/api/useApiFetch'; ...@@ -13,35 +20,88 @@ import useApiFetch from 'lib/api/useApiFetch';
import AddressVerificationFieldMessage from '../fields/AddressVerificationFieldMessage'; import AddressVerificationFieldMessage from '../fields/AddressVerificationFieldMessage';
import AddressVerificationFieldSignature from '../fields/AddressVerificationFieldSignature'; import AddressVerificationFieldSignature from '../fields/AddressVerificationFieldSignature';
interface Props extends AddressVerificationFormFirstStepFields, AddressCheckStatusSuccess{} type Fields = RootFields & AddressVerificationFormSecondStepFields;
const AddressVerificationStepSignature = ({ address, signingMessage }: Props) => { interface Props extends AddressVerificationFormFirstStepFields, AddressCheckStatusSuccess{
onContinue: () => void;
}
const AddressVerificationStepSignature = ({ address, signingMessage, contractCreator, contractOwner, onContinue }: Props) => {
const [ signMethod, setSignMethod ] = React.useState<'wallet' | 'manually'>('wallet'); const [ signMethod, setSignMethod ] = React.useState<'wallet' | 'manually'>('wallet');
const [ error, setError ] = React.useState('');
const formApi = useForm<AddressVerificationFormSecondStepFields>({ const formApi = useForm<Fields>({
mode: 'onBlur', mode: 'onBlur',
defaultValues: { defaultValues: {
message: signingMessage, message: signingMessage,
}, },
}); });
const { handleSubmit, formState, control, setValue, getValues } = formApi; const { handleSubmit, formState, control, setValue, getValues, setError, clearErrors, watch } = formApi;
const apiFetch = useApiFetch(); const apiFetch = useApiFetch();
const signature = watch('signature');
React.useEffect(() => {
clearErrors('root');
}, [ clearErrors, signature ]);
const onFormSubmit: SubmitHandler<Fields> = React.useCallback(async(data) => {
try {
const body = {
contractAddress: address,
message: data.message,
signature: data.signature,
};
const response = await apiFetch<'address_verification', AddressValidationResponseSuccess, AddressVerificationResponseError>('address_verification', {
fetchParams: { method: 'POST', body },
pathParams: { chainId: appConfig.network.id, type: ':verify' },
});
if (response.status !== 'SUCCESS') {
switch (response.status) {
case 'INVALID_SIGNATURE_ERROR': {
return setError('root', { type: 'manual', message: 'Invalid signature' });
}
case 'VALIDITY_EXPIRED_ERROR': {
return setError('root', { type: 'manual', message: 'Message validity expired' });
}
case 'INVALID_SIGNER_ERROR': {
const message = `Invalid signer ${ response.invalidSigner.signer }. Expected: ${ response.invalidSigner.validAddresses.join(', ') }.`;
return setError('root', { type: 'manual', message });
}
case 'UNKNOWN_STATUS': {
return setError('root', { type: 'manual', message: 'Oops! Something went wrong' });
}
default: {
return setError('root', { type: 'manual', message: response.payload?.message || 'Oops! Something went wrong' });
}
}
}
onContinue();
} catch (_error: unknown) {
const error = _error as ResourceError<AddressVerificationResponseError>;
setError('root', { type: 'manual', message: error.payload?.message || 'Oops! Something went wrong' });
}
}, [ address, apiFetch, onContinue, setError ]);
const onSubmit = handleSubmit(onFormSubmit);
const { signMessage, isLoading: isSigning } = useSignMessage({ const { signMessage, isLoading: isSigning } = useSignMessage({
onSuccess: (data) => { onSuccess: (data) => {
setValue('signature', data); setValue('signature', data);
onSubmit();
}, },
onError: (error) => { onError: (error) => {
setError((error as Error)?.message || 'Something went wrong'); return setError('root', { type: 'manual', message: (error as Error)?.message || 'Oops! Something went wrong' });
}, },
}); });
const handleSignMethodChange = React.useCallback((value: typeof signMethod) => { const handleSignMethodChange = React.useCallback((value: typeof signMethod) => {
setSignMethod(value); setSignMethod(value);
setError(''); clearErrors('root');
}, []); }, [ clearErrors ]);
const handleWeb3SignClick = React.useCallback(() => { const handleWeb3SignClick = React.useCallback(() => {
const message = getValues('message'); const message = getValues('message');
...@@ -49,34 +109,32 @@ const AddressVerificationStepSignature = ({ address, signingMessage }: Props) => ...@@ -49,34 +109,32 @@ const AddressVerificationStepSignature = ({ address, signingMessage }: Props) =>
}, [ getValues, signMessage ]); }, [ getValues, signMessage ]);
const handleManualSignClick = React.useCallback(() => { const handleManualSignClick = React.useCallback(() => {
}, []); onSubmit();
}, [ onSubmit ]);
const onFormSubmit: SubmitHandler<AddressVerificationFormSecondStepFields> = React.useCallback(async(data) => {
const body = {
contractAddress: address,
message: data.message,
signature: data.signature,
};
try {
await apiFetch('address_verification', {
fetchParams: { method: 'POST', body },
pathParams: { chainId: appConfig.network.id, type: ':verify' },
});
} catch (error: unknown) {
const _error = error as ResourceError<{message: string}>;
setError(_error.payload?.message || 'Oops! Something went wrong');
}
}, [ address, apiFetch ]);
const onSubmit = handleSubmit(onFormSubmit);
return ( return (
<form noValidate onSubmit={ onSubmit }> <form noValidate onSubmit={ onSubmit }>
{ error && <Alert status="warning" mb={ 6 }>{ error }</Alert> } { formState.errors.root?.type === 'manual' && <Alert status="warning" mb={ 6 }>{ formState.errors.root.message }</Alert> }
<Box mb={ 8 }> <Box mb={ 8 }>
Please select the address to sign and copy the message below and sign it using the Blockscout sign message provider of your choice... <span>Please select the address below you will use to sign, copy the message, and sign it using your preferred method. </span>
<Link>Additional instructions</Link>
</Box> </Box>
{ (contractOwner || contractCreator) && (
<Flex flexDir="column" rowGap={ 4 } mb={ 8 }>
{ contractCreator && (
<Box>
<chakra.span fontWeight={ 600 }>Contract creator: </chakra.span>
<chakra.span>{ contractCreator }</chakra.span>
</Box>
) }
{ contractOwner && (
<Box>
<chakra.span fontWeight={ 600 }>Contract owner: </chakra.span>
<chakra.span>{ contractOwner }</chakra.span>
</Box>
) }
</Flex>
) }
<Flex rowGap={ 5 } flexDir="column"> <Flex rowGap={ 5 } flexDir="column">
<AddressVerificationFieldMessage formState={ formState } control={ control }/> <AddressVerificationFieldMessage formState={ formState } control={ control }/>
<RadioGroup onChange={ handleSignMethodChange } value={ signMethod } display="flex" flexDir="column" rowGap={ 4 }> <RadioGroup onChange={ handleSignMethodChange } value={ signMethod } display="flex" flexDir="column" rowGap={ 4 }>
......
...@@ -26,7 +26,29 @@ export type AddressCheckResponseSuccess = { ...@@ -26,7 +26,29 @@ export type AddressCheckResponseSuccess = {
{ status: 'SOURCE_CODE_NOT_VERIFIED_ERROR' } | { status: 'SOURCE_CODE_NOT_VERIFIED_ERROR' } |
{ status: 'INVALID_ADDRESS_ERROR' }; { status: 'INVALID_ADDRESS_ERROR' };
export interface AddressCheckResponseError { export interface AddressVerificationResponseError {
code: number; code: number;
message: string; message: string;
} }
export type AddressValidationResponseSuccess = {
status: 'SUCCESS';
result: {
verifiedAddress: {
chainId: string;
contractAddress: string;
userId: string;
verifiedDate: string;
};
};
} |
{
status: 'INVALID_SIGNER_ERROR';
invalidSigner: {
signer: string;
validAddresses: Array<string>;
};
} |
{ status: 'VALIDITY_EXPIRED_ERROR' } |
{ status: 'INVALID_SIGNATURE_ERROR' } |
{ status: 'UNKNOWN_STATUS' }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment