Commit 25bd3615 authored by tom's avatar tom

error handling

parent 77621de2
......@@ -2,6 +2,7 @@ import type { Channel } from 'phoenix';
import type { AddressCoinBalanceHistoryItem } from 'types/api/address';
import type { NewBlockSocketResponse } from 'types/api/block';
import type { SmartContractVerificationResponse } from 'types/api/contract';
import type { TokenTransfer } from 'types/api/tokenTransfer';
import type { Transaction } from 'types/api/transaction';
......@@ -45,6 +46,6 @@ export namespace SocketMessage {
export type AddressTxsPending = SocketMessageParamsGeneric<'pending_transaction', { transaction: Transaction }>;
export type AddressTokenTransfer = SocketMessageParamsGeneric<'token_transfer', { token_transfer: TokenTransfer }>;
export type TokenTransfers = SocketMessageParamsGeneric<'token_transfer', {token_transfer: number }>;
export type ContractVerification = SocketMessageParamsGeneric<'verification_result', unknown>;
export type ContractVerification = SocketMessageParamsGeneric<'verification_result', SmartContractVerificationResponse>;
export type Unknown = SocketMessageParamsGeneric<undefined, unknown>;
}
......@@ -129,3 +129,18 @@ export interface SmartContractVerificationConfigRaw {
export interface SmartContractVerificationConfig extends SmartContractVerificationConfigRaw {
verification_options: Array<SmartContractVerificationMethod>;
}
export type SmartContractVerificationResponse = {
status: 'error';
errors: SmartContractVerificationError;
} | {
status: 'success';
}
export interface SmartContractVerificationError {
contract_source_code?: Array<string>;
files?: Array<string>;
compiler_version?: Array<string>;
constructor_arguments?: Array<string>;
name?: Array<string>;
}
......@@ -8,6 +8,7 @@ import type { SocketMessage } from 'lib/socket/types';
import type { SmartContractVerificationMethod, SmartContractVerificationConfig } from 'types/api/contract';
import useApiFetch from 'lib/api/useApiFetch';
import useToast from 'lib/hooks/useToast';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
......@@ -18,7 +19,7 @@ import ContractVerificationSourcify from './methods/ContractVerificationSourcify
import ContractVerificationStandardInput from './methods/ContractVerificationStandardInput';
import ContractVerificationVyperContract from './methods/ContractVerificationVyperContract';
import ContractVerificationVyperMultiPartFile from './methods/ContractVerificationVyperMultiPartFile';
import { prepareRequestBody, METHOD_TO_ENDPOINT_MAP } from './utils';
import { prepareRequestBody, METHOD_TO_ENDPOINT_MAP, formatSocketErrors } from './utils';
const METHOD_COMPONENTS = {
flattened_code: <ContractVerificationFlattenSourceCode/>,
......@@ -42,10 +43,11 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro
method: methodFromQuery,
},
});
const { control, handleSubmit, watch, formState } = formApi;
const { control, handleSubmit, watch, formState, setError } = formApi;
const submitPromiseResolver = React.useRef<(value: unknown) => void>();
const apiFetch = useApiFetch();
const toast = useToast();
const onFormSubmit: SubmitHandler<FormFields> = React.useCallback(async(data) => {
// eslint-disable-next-line no-console
......@@ -72,22 +74,40 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro
const handleNewSocketMessage: SocketMessage.ContractVerification['handler'] = React.useCallback((payload) => {
// eslint-disable-next-line no-console
console.log('__>__', payload);
}, []);
console.log('__>__ handleNewSocketMessage', payload);
if (payload.status === 'error') {
const errors = formatSocketErrors(payload.errors);
errors.forEach(([ field, error ]) => setError(field, error));
}
submitPromiseResolver.current?.(null);
}, [ setError ]);
const handleSocketError = React.useCallback(() => {
submitPromiseResolver.current?.(null);
}, []);
const toastId = 'socket-error';
!toast.isActive(toastId) && toast({
id: toastId,
position: 'top-right',
title: 'Error',
description: 'There was an error with socket connection. Try again later.',
status: 'error',
variant: 'subtle',
isClosable: true,
});
}, [ toast ]);
const channel = useSocketChannel({
topic: `address:${ hash }`,
topic: `addresses:${ hash.toLowerCase() }`,
onSocketClose: handleSocketError,
onSocketError: handleSocketError,
isDisabled: !formState.isSubmitting,
});
useSocketMessage({
channel,
event: 'verification',
event: 'verification_result',
handler: handleNewSocketMessage,
});
......
......@@ -5,6 +5,7 @@ import { useFormContext, Controller } from 'react-hook-form';
import type { FormFields } from '../types';
import FieldError from 'ui/shared/forms/FieldError';
import InputPlaceholder from 'ui/shared/InputPlaceholder';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
......@@ -27,7 +28,8 @@ const ContractVerificationFieldCode = ({ isVyper }: Props) => {
isDisabled={ formState.isSubmitting }
required
/>
<InputPlaceholder text="Contract code" error={ error }/>
<InputPlaceholder text="Contract code"/>
{ error?.message && <FieldError message={ error?.message }/> }
</FormControl>
);
}, [ formState.errors, formState.isSubmitting ]);
......
......@@ -5,6 +5,7 @@ import { Controller, useFormContext } from 'react-hook-form';
import type { FormFields } from '../types';
import FieldError from 'ui/shared/forms/FieldError';
import InputPlaceholder from 'ui/shared/InputPlaceholder';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
......@@ -13,17 +14,22 @@ const ContractVerificationFieldConstructorArgs = () => {
const { formState, control } = useFormContext<FormFields>();
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'constructor_args'>}) => {
const error = 'constructor_args' in formState.errors ? formState.errors.constructor_args : undefined;
return (
<FormControl variant="floating" id={ field.name } size={{ base: 'md', lg: 'lg' }}>
<FormControl variant="floating" id={ field.name } size={{ base: 'md', lg: 'lg' }} isRequired>
<Textarea
{ ...field }
maxLength={ 255 }
isDisabled={ formState.isSubmitting }
isInvalid={ Boolean(error) }
required
/>
<InputPlaceholder text="ABI-encoded Constructor Arguments"/>
{ error?.message && <FieldError message={ error?.message }/> }
</FormControl>
);
}, [ formState.isSubmitting ]);
}, [ formState.errors, formState.isSubmitting ]);
return (
<ContractVerificationFormRow>
......@@ -31,6 +37,7 @@ const ContractVerificationFieldConstructorArgs = () => {
name="constructor_args"
control={ control }
render={ renderControl }
rules={{ required: true }}
/>
<>
<span>Add arguments in </span>
......
import type { FieldPath, ErrorOption } from 'react-hook-form';
import type { ContractLibrary, FormFields } from './types';
import type { SmartContractVerificationMethod } from 'types/api/contract';
import type { SmartContractVerificationMethod, SmartContractVerificationError } from 'types/api/contract';
import type { Params as FetchParams } from 'lib/hooks/useFetch';
......@@ -44,13 +46,13 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
switch (data.method) {
case 'flattened_code': {
return {
compiler_version: data.compiler.value,
compiler_version: data.compiler?.value,
source_code: data.code,
is_optimization_enabled: data.is_optimization_enabled,
optimization_runs: data.optimization_runs,
contract_name: data.name,
libraries: reduceLibrariesArray(data.libraries),
evm_version: data.evm_version.value,
evm_version: data.evm_version?.value,
autodetect_constructor_args: data.autodetect_constructor_args,
constructor_args: data.constructor_args,
};
......@@ -58,7 +60,7 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
case 'standard_input': {
const body = new FormData();
body.set('compiler_version', data.compiler.value);
body.set('compiler_version', data.compiler?.value);
body.set('contract_name', data.name);
body.set('autodetect_constructor_args', String(Boolean(data.autodetect_constructor_args)));
body.set('constructor_args', data.constructor_args);
......@@ -76,8 +78,8 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
case 'multi_part': {
const body = new FormData();
body.set('compiler_version', data.compiler.value);
body.set('evm_version', data.evm_version.value);
body.set('compiler_version', data.compiler?.value);
body.set('evm_version', data.evm_version?.value);
body.set('is_optimization_enabled', String(Boolean(data.is_optimization_enabled)));
data.is_optimization_enabled && body.set('optimization_runs', data.optimization_runs);
......@@ -90,7 +92,7 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
case 'vyper_code': {
return {
compiler_version: data.compiler.value,
compiler_version: data.compiler?.value,
source_code: data.code,
contract_name: data.name,
constructor_args: data.constructor_args,
......@@ -99,8 +101,8 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
case 'vyper_multi_part': {
const body = new FormData();
body.set('compiler_version', data.compiler.value);
body.set('evm_version', data.evm_version.value);
body.set('compiler_version', data.compiler?.value);
body.set('evm_version', data.evm_version?.value);
addFilesToFormData(body, data.sources);
return body;
......@@ -123,9 +125,27 @@ function reduceLibrariesArray(libraries: Array<ContractLibrary> | undefined) {
}, {});
}
function addFilesToFormData(body: FormData, files: Array<File>) {
function addFilesToFormData(body: FormData, files: Array<File> | undefined) {
if (!files) {
return;
}
for (let index = 0; index < files.length; index++) {
const file = files[index];
body.set(`files[${ index }]`, file, file.name);
}
}
const API_ERROR_TO_FORM_FIELD: Record<keyof SmartContractVerificationError, FieldPath<FormFields>> = {
contract_source_code: 'code',
files: 'sources',
compiler_version: 'compiler',
constructor_arguments: 'constructor_args',
name: 'name',
};
export function formatSocketErrors(errors: SmartContractVerificationError): Array<[FieldPath<FormFields>, ErrorOption]> {
return Object.entries(errors).map(([ key, value ]) => {
return [ API_ERROR_TO_FORM_FIELD[key as keyof SmartContractVerificationError], { message: value.join(',') } ];
});
}
import { Box, chakra } from '@chakra-ui/react';
import React from 'react';
interface Props {
message: string;
className?: string;
}
const FieldError = ({ message, className }: Props) => {
return <Box className={ className } color="error" fontSize="sm" mt={ 2 } wordBreak="break-all">{ message }</Box>;
};
export default chakra(FieldError);
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