Commit 532f7e7a authored by tom's avatar tom

contract verification: flattened code method

parent cdb1793c
...@@ -29,6 +29,7 @@ const RESTRICTED_MODULES = { ...@@ -29,6 +29,7 @@ const RESTRICTED_MODULES = {
{ name: 'ui/shared/Tabs/RoutedTabs', message: 'Please use RoutedTabs component from toolkit/components/RoutedTabs instead' }, { name: 'ui/shared/Tabs/RoutedTabs', message: 'Please use RoutedTabs component from toolkit/components/RoutedTabs instead' },
{ name: 'ui/shared/chakra/Tag', message: 'Please use Tag component from toolkit/chakra instead' }, { name: 'ui/shared/chakra/Tag', message: 'Please use Tag component from toolkit/chakra instead' },
{ name: 'ui/shared/select/Select', message: 'Please use Select component from toolkit/chakra instead' }, { name: 'ui/shared/select/Select', message: 'Please use Select component from toolkit/chakra instead' },
{ name: 'ui/shared/forms/fields/FormFieldFancySelect', message: 'Please use FormFieldSelect component' },
{ {
name: '@chakra-ui/react', name: '@chakra-ui/react',
importNames: [ importNames: [
......
...@@ -4,12 +4,12 @@ import React from 'react'; ...@@ -4,12 +4,12 @@ import React from 'react';
import type { Props } from 'nextjs/getServerSideProps'; import type { Props } from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs'; import PageNextJs from 'nextjs/PageNextJs';
// import ContractVerificationForAddress from 'ui/pages/ContractVerificationForAddress'; import ContractVerificationForAddress from 'ui/pages/ContractVerificationForAddress';
const Page: NextPage<Props> = (props: Props) => { const Page: NextPage<Props> = (props: Props) => {
return ( return (
<PageNextJs pathname="/address/[hash]/contract-verification" query={ props.query }> <PageNextJs pathname="/address/[hash]/contract-verification" query={ props.query }>
{ /* <ContractVerificationForAddress/> */ } <ContractVerificationForAddress/>
</PageNextJs> </PageNextJs>
); );
}; };
......
...@@ -4,12 +4,12 @@ import React from 'react'; ...@@ -4,12 +4,12 @@ import React from 'react';
import type { Props } from 'nextjs/getServerSideProps'; import type { Props } from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs'; import PageNextJs from 'nextjs/PageNextJs';
// import ContractVerification from 'ui/pages/ContractVerification'; import ContractVerification from 'ui/pages/ContractVerification';
const Page: NextPage<Props> = (props: Props) => { const Page: NextPage<Props> = (props: Props) => {
return ( return (
<PageNextJs pathname="/contract-verification" query={ props.query }> <PageNextJs pathname="/contract-verification" query={ props.query }>
{ /* <ContractVerification/> */ } <ContractVerification/>
</PageNextJs> </PageNextJs>
); );
}; };
......
...@@ -10,6 +10,7 @@ import { recipe as drawer } from './drawer.recipe'; ...@@ -10,6 +10,7 @@ import { recipe as drawer } from './drawer.recipe';
import { recipe as field } from './field.recipe'; import { recipe as field } from './field.recipe';
import { recipe as input } from './input.recipe'; import { recipe as input } from './input.recipe';
import { recipe as link } from './link.recipe'; import { recipe as link } from './link.recipe';
import { recipe as list } from './list.recipe';
import { recipe as menu } from './menu.recipe'; import { recipe as menu } from './menu.recipe';
import { recipe as nativeSelect } from './native-select.recipe'; import { recipe as nativeSelect } from './native-select.recipe';
import { recipe as pinInput } from './pin-input.recipe'; import { recipe as pinInput } from './pin-input.recipe';
...@@ -49,6 +50,7 @@ export const slotRecipes = { ...@@ -49,6 +50,7 @@ export const slotRecipes = {
dialog, dialog,
drawer, drawer,
field, field,
list,
menu, menu,
nativeSelect, nativeSelect,
pinInput, pinInput,
......
...@@ -64,6 +64,9 @@ export const recipe = defineRecipe({ ...@@ -64,6 +64,9 @@ export const recipe = defineRecipe({
}, },
_placeholderShown: { _placeholderShown: {
borderColor: 'input.border', borderColor: 'input.border',
_invalid: {
borderColor: 'input.border.error',
},
}, },
_hover: { _hover: {
borderColor: 'input.border.hover', borderColor: 'input.border.hover',
......
import { defineSlotRecipe } from '@chakra-ui/react';
export const recipe = defineSlotRecipe({
slots: [ 'root', 'item', 'indicator' ],
base: {
root: {
display: 'flex',
flexDirection: 'column',
gap: 'var(--list-gap)',
'& :where(ul, ol)': {
marginTop: 'var(--list-gap)',
},
},
item: {
whiteSpace: 'normal',
display: 'list-item',
'&::marker': {
color: 'inherit',
},
},
indicator: {
marginEnd: '2',
minHeight: '1lh',
flexShrink: 0,
display: 'inline-block',
verticalAlign: 'middle',
},
},
variants: {
variant: {
marker: {
root: {
listStyle: 'revert',
},
item: {
_marker: {
color: 'inherit',
},
},
},
plain: {
item: {
alignItems: 'flex-start',
display: 'inline-flex',
},
},
},
align: {
center: {
item: { alignItems: 'center' },
},
start: {
item: { alignItems: 'flex-start' },
},
end: {
item: { alignItems: 'flex-end' },
},
},
},
defaultVariants: {
variant: 'marker',
},
});
import { Button, Grid, Text, chakra, useUpdateEffect } from '@chakra-ui/react'; import { Grid, Text, chakra, useUpdateEffect } 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, FormProvider } from 'react-hook-form'; import { useForm, FormProvider } from 'react-hook-form';
...@@ -14,24 +14,25 @@ import useApiFetch from 'lib/api/useApiFetch'; ...@@ -14,24 +14,25 @@ import useApiFetch from 'lib/api/useApiFetch';
import capitalizeFirstLetter from 'lib/capitalizeFirstLetter'; import capitalizeFirstLetter from 'lib/capitalizeFirstLetter';
import delay from 'lib/delay'; import delay from 'lib/delay';
import getErrorObjStatusCode from 'lib/errors/getErrorObjStatusCode'; import getErrorObjStatusCode from 'lib/errors/getErrorObjStatusCode';
import useToast from 'lib/hooks/useToast';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import useSocketChannel from 'lib/socket/useSocketChannel'; import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage'; import useSocketMessage from 'lib/socket/useSocketMessage';
import { Button } from 'toolkit/chakra/button';
import { toaster } from 'toolkit/chakra/toaster';
import ContractVerificationFieldAddress from './fields/ContractVerificationFieldAddress'; import ContractVerificationFieldAddress from './fields/ContractVerificationFieldAddress';
import ContractVerificationFieldLicenseType from './fields/ContractVerificationFieldLicenseType'; import ContractVerificationFieldLicenseType from './fields/ContractVerificationFieldLicenseType';
import ContractVerificationFieldMethod from './fields/ContractVerificationFieldMethod'; import ContractVerificationFieldMethod from './fields/ContractVerificationFieldMethod';
import ContractVerificationFlattenSourceCode from './methods/ContractVerificationFlattenSourceCode'; import ContractVerificationFlattenSourceCode from './methods/ContractVerificationFlattenSourceCode';
import ContractVerificationMultiPartFile from './methods/ContractVerificationMultiPartFile'; // import ContractVerificationMultiPartFile from './methods/ContractVerificationMultiPartFile';
import ContractVerificationSolidityFoundry from './methods/ContractVerificationSolidityFoundry'; // import ContractVerificationSolidityFoundry from './methods/ContractVerificationSolidityFoundry';
import ContractVerificationSolidityHardhat from './methods/ContractVerificationSolidityHardhat'; // import ContractVerificationSolidityHardhat from './methods/ContractVerificationSolidityHardhat';
import ContractVerificationSourcify from './methods/ContractVerificationSourcify'; // import ContractVerificationSourcify from './methods/ContractVerificationSourcify';
import ContractVerificationStandardInput from './methods/ContractVerificationStandardInput'; // import ContractVerificationStandardInput from './methods/ContractVerificationStandardInput';
import ContractVerificationStylusGitHubRepo from './methods/ContractVerificationStylusGitHubRepo'; // import ContractVerificationStylusGitHubRepo from './methods/ContractVerificationStylusGitHubRepo';
import ContractVerificationVyperContract from './methods/ContractVerificationVyperContract'; // import ContractVerificationVyperContract from './methods/ContractVerificationVyperContract';
import ContractVerificationVyperMultiPartFile from './methods/ContractVerificationVyperMultiPartFile'; // import ContractVerificationVyperMultiPartFile from './methods/ContractVerificationVyperMultiPartFile';
import ContractVerificationVyperStandardInput from './methods/ContractVerificationVyperStandardInput'; // import ContractVerificationVyperStandardInput from './methods/ContractVerificationVyperStandardInput';
import { prepareRequestBody, formatSocketErrors, getDefaultValues, METHOD_LABELS } from './utils'; import { prepareRequestBody, formatSocketErrors, getDefaultValues, METHOD_LABELS } from './utils';
interface Props { interface Props {
...@@ -43,14 +44,13 @@ interface Props { ...@@ -43,14 +44,13 @@ interface Props {
const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Props) => { const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Props) => {
const formApi = useForm<FormFields>({ const formApi = useForm<FormFields>({
mode: 'onBlur', mode: 'onBlur',
defaultValues: getDefaultValues(methodFromQuery, config, hash, null), defaultValues: getDefaultValues(methodFromQuery, config, hash, []),
}); });
const { handleSubmit, watch, formState, setError, reset, getFieldState, getValues, clearErrors } = formApi; const { handleSubmit, watch, formState, setError, reset, getFieldState, getValues, clearErrors } = formApi;
const submitPromiseResolver = React.useRef<(value: unknown) => void>(); const submitPromiseResolver = React.useRef<(value: unknown) => void>();
const methodNameRef = React.useRef<string>(); const methodNameRef = React.useRef<string>();
const apiFetch = useApiFetch(); const apiFetch = useApiFetch();
const toast = useToast();
const onFormSubmit: SubmitHandler<FormFields> = React.useCallback(async(data) => { const onFormSubmit: SubmitHandler<FormFields> = React.useCallback(async(data) => {
const body = prepareRequestBody(data); const body = prepareRequestBody(data);
...@@ -76,7 +76,7 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro ...@@ -76,7 +76,7 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro
try { try {
await apiFetch('contract_verification_via', { await apiFetch('contract_verification_via', {
pathParams: { method: data.method.value, hash: data.address.toLowerCase() }, pathParams: { method: data.method[0], hash: data.address.toLowerCase() },
fetchParams: { fetchParams: {
method: 'POST', method: 'POST',
body, body,
...@@ -116,13 +116,9 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro ...@@ -116,13 +116,9 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro
return; return;
} }
toast({ toaster.success({
position: 'top-right',
title: 'Success', title: 'Success',
description: 'Contract is successfully verified.', description: 'Contract is successfully verified.',
status: 'success',
variant: 'subtle',
isClosable: true,
}); });
mixpanel.logEvent( mixpanel.logEvent(
...@@ -132,7 +128,7 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro ...@@ -132,7 +128,7 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro
); );
window.location.assign(route({ pathname: '/address/[hash]', query: { hash: address, tab: 'contract' } })); window.location.assign(route({ pathname: '/address/[hash]', query: { hash: address, tab: 'contract' } }));
}, [ setError, toast, address, getValues ]); }, [ setError, address, getValues ]);
const handleSocketError = React.useCallback(() => { const handleSocketError = React.useCallback(() => {
if (!formState.isSubmitting) { if (!formState.isSubmitting) {
...@@ -141,20 +137,14 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro ...@@ -141,20 +137,14 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro
submitPromiseResolver.current?.(null); submitPromiseResolver.current?.(null);
const toastId = 'socket-error'; toaster.error({
!toast.isActive(toastId) && toast({
id: toastId,
position: 'top-right',
title: 'Error', title: 'Error',
description: 'There was an error with socket connection. Try again later.', description: 'There was an error with socket connection. Try again later.',
status: 'error',
variant: 'subtle',
isClosable: true,
}); });
// callback should not change when form is submitted // callback should not change when form is submitted
// otherwise it will resubscribe to channel, but we don't want that since in that case we might miss verification result message // otherwise it will resubscribe to channel, but we don't want that since in that case we might miss verification result message
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ toast ]); }, [ ]);
const channel = useSocketChannel({ const channel = useSocketChannel({
topic: `addresses:${ address?.toLowerCase() }`, topic: `addresses:${ address?.toLowerCase() }`,
...@@ -171,21 +161,21 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro ...@@ -171,21 +161,21 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro
const methods = React.useMemo(() => { const methods = React.useMemo(() => {
return { return {
'flattened-code': <ContractVerificationFlattenSourceCode config={ config }/>, 'flattened-code': <ContractVerificationFlattenSourceCode config={ config }/>,
'standard-input': <ContractVerificationStandardInput config={ config }/>, // 'standard-input': <ContractVerificationStandardInput config={ config }/>,
sourcify: <ContractVerificationSourcify/>, // sourcify: <ContractVerificationSourcify/>,
'multi-part': <ContractVerificationMultiPartFile/>, // 'multi-part': <ContractVerificationMultiPartFile/>,
'vyper-code': <ContractVerificationVyperContract config={ config }/>, // 'vyper-code': <ContractVerificationVyperContract config={ config }/>,
'vyper-multi-part': <ContractVerificationVyperMultiPartFile/>, // 'vyper-multi-part': <ContractVerificationVyperMultiPartFile/>,
'vyper-standard-input': <ContractVerificationVyperStandardInput/>, // 'vyper-standard-input': <ContractVerificationVyperStandardInput/>,
'solidity-hardhat': <ContractVerificationSolidityHardhat config={ config }/>, // 'solidity-hardhat': <ContractVerificationSolidityHardhat config={ config }/>,
'solidity-foundry': <ContractVerificationSolidityFoundry/>, // 'solidity-foundry': <ContractVerificationSolidityFoundry/>,
'stylus-github-repository': <ContractVerificationStylusGitHubRepo/>, // 'stylus-github-repository': <ContractVerificationStylusGitHubRepo/>,
}; };
}, [ config ]); }, [ config ]);
const method = watch('method'); const method = watch('method');
const methodValue = method?.[0];
const licenseType = watch('license_type'); const licenseType = watch('license_type');
const content = methods[method?.value] || null; const content = methods[methodValue] || null;
const methodValue = method?.value;
useUpdateEffect(() => { useUpdateEffect(() => {
if (methodValue) { if (methodValue) {
...@@ -212,13 +202,12 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro ...@@ -212,13 +202,12 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro
</Grid> </Grid>
{ content } { content }
{ formState.errors.root?.message && <Text color="error"mt={ 4 } fontSize="sm" whiteSpace="pre-wrap">{ formState.errors.root.message }</Text> } { formState.errors.root?.message && <Text color="error"mt={ 4 } fontSize="sm" whiteSpace="pre-wrap">{ formState.errors.root.message }</Text> }
{ Boolean(method) && method.value !== 'solidity-hardhat' && method.value !== 'solidity-foundry' && ( { Boolean(method) && methodValue !== 'solidity-hardhat' && methodValue !== 'solidity-foundry' && (
<Button <Button
variant="solid" size="md"
size="lg"
type="submit" type="submit"
mt={ 12 } mt={ 12 }
isLoading={ formState.isSubmitting } loading={ formState.isSubmitting }
loadingText="Verify & publish" loadingText="Verify & publish"
> >
Verify & publish Verify & publish
......
...@@ -8,10 +8,10 @@ import FormFieldAddress from 'ui/shared/forms/fields/FormFieldAddress'; ...@@ -8,10 +8,10 @@ import FormFieldAddress from 'ui/shared/forms/fields/FormFieldAddress';
import ContractVerificationFormRow from '../ContractVerificationFormRow'; import ContractVerificationFormRow from '../ContractVerificationFormRow';
interface Props { interface Props {
isReadOnly?: boolean; readOnly?: boolean;
} }
const ContractVerificationFieldAddress = ({ isReadOnly }: Props) => { const ContractVerificationFieldAddress = ({ readOnly }: Props) => {
return ( return (
<> <>
<ContractVerificationFormRow> <ContractVerificationFormRow>
...@@ -22,10 +22,9 @@ const ContractVerificationFieldAddress = ({ isReadOnly }: Props) => { ...@@ -22,10 +22,9 @@ const ContractVerificationFieldAddress = ({ isReadOnly }: Props) => {
<ContractVerificationFormRow> <ContractVerificationFormRow>
<FormFieldAddress<FormFields> <FormFieldAddress<FormFields>
name="address" name="address"
isRequired required
placeholder="Smart contract / Address (0x...)" placeholder="Smart contract / Address (0x...)"
isReadOnly={ isReadOnly } readOnly={ readOnly }
size={{ base: 'md', lg: 'lg' }}
/> />
</ContractVerificationFormRow> </ContractVerificationFormRow>
</> </>
......
...@@ -15,9 +15,8 @@ const ContractVerificationFieldCode = ({ isVyper }: Props) => { ...@@ -15,9 +15,8 @@ const ContractVerificationFieldCode = ({ isVyper }: Props) => {
<ContractVerificationFormRow> <ContractVerificationFormRow>
<FormFieldText<FormFields> <FormFieldText<FormFields>
name="code" name="code"
isRequired required
placeholder="Contract code" placeholder="Contract code"
size={{ base: 'md', lg: 'lg' }}
asComponent="Textarea" asComponent="Textarea"
/> />
{ isVyper ? null : ( { isVyper ? null : (
......
import { chakra, Checkbox, Code } from '@chakra-ui/react'; import { chakra, Code, createListCollection } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import React from 'react'; import React from 'react';
import { useFormContext } from 'react-hook-form'; import { useFormContext } from 'react-hook-form';
...@@ -7,8 +7,8 @@ import type { FormFields } from '../types'; ...@@ -7,8 +7,8 @@ import type { FormFields } from '../types';
import type { SmartContractVerificationConfig } from 'types/client/contract'; import type { SmartContractVerificationConfig } from 'types/client/contract';
import { getResourceKey } from 'lib/api/useApiQuery'; import { getResourceKey } from 'lib/api/useApiQuery';
import FormFieldFancySelect from 'ui/shared/forms/fields/FormFieldFancySelect'; import { Checkbox } from 'toolkit/chakra/checkbox';
import IconSvg from 'ui/shared/IconSvg'; import FormFieldSelect from 'ui/shared/forms/fields/FormFieldSelect';
import ContractVerificationFormRow from '../ContractVerificationFormRow'; import ContractVerificationFormRow from '../ContractVerificationFormRow';
...@@ -26,12 +26,15 @@ const ContractVerificationFieldCompiler = ({ isVyper, isStylus }: Props) => { ...@@ -26,12 +26,15 @@ const ContractVerificationFieldCompiler = ({ isVyper, isStylus }: Props) => {
const config = queryClient.getQueryData<SmartContractVerificationConfig>(getResourceKey('contract_verification_config')); const config = queryClient.getQueryData<SmartContractVerificationConfig>(getResourceKey('contract_verification_config'));
const handleCheckboxChange = React.useCallback(() => { const handleCheckboxChange = React.useCallback(() => {
if (isNightly) { setIsNightly(prev => {
if (prev) {
const field = getValues('compiler'); const field = getValues('compiler');
field?.value.includes('nightly') && resetField('compiler', { defaultValue: null }); field?.[0]?.includes('nightly') && resetField('compiler', { defaultValue: [] });
} }
setIsNightly(prev => !prev);
}, [ getValues, isNightly, resetField ]); return !prev;
});
}, [ getValues, resetField ]);
const options = React.useMemo(() => { const options = React.useMemo(() => {
const versions = (() => { const versions = (() => {
...@@ -47,11 +50,21 @@ const ContractVerificationFieldCompiler = ({ isVyper, isStylus }: Props) => { ...@@ -47,11 +50,21 @@ const ContractVerificationFieldCompiler = ({ isVyper, isStylus }: Props) => {
return versions?.map((option) => ({ label: option, value: option })) || []; return versions?.map((option) => ({ label: option, value: option })) || [];
}, [ isStylus, isVyper, config?.solidity_compiler_versions, config?.stylus_compiler_versions, config?.vyper_compiler_versions ]); }, [ isStylus, isVyper, config?.solidity_compiler_versions, config?.stylus_compiler_versions, config?.vyper_compiler_versions ]);
const loadOptions = React.useCallback(async(inputValue: string) => { // const loadOptions = React.useCallback(async(inputValue: string) => {
return options // return options
.filter(({ label }) => !inputValue || label.toLowerCase().includes(inputValue.toLowerCase())) // .filter(({ label }) => !inputValue || label.toLowerCase().includes(inputValue.toLowerCase()))
// .filter(({ label }) => isNightly ? true : !label.includes('nightly'))
// .slice(0, OPTIONS_LIMIT);
// }, [ isNightly, options ]);
// TODO @tom2drum implement filtering the options
const collection = React.useMemo(() => {
const items = options
// .filter(({ label }) => !inputValue || label.toLowerCase().includes(inputValue.toLowerCase()))
.filter(({ label }) => isNightly ? true : !label.includes('nightly')) .filter(({ label }) => isNightly ? true : !label.includes('nightly'))
.slice(0, OPTIONS_LIMIT); .slice(0, OPTIONS_LIMIT);
return createListCollection({ items });
}, [ isNightly, options ]); }, [ isNightly, options ]);
return ( return (
...@@ -59,22 +72,19 @@ const ContractVerificationFieldCompiler = ({ isVyper, isStylus }: Props) => { ...@@ -59,22 +72,19 @@ const ContractVerificationFieldCompiler = ({ isVyper, isStylus }: Props) => {
<> <>
{ !isVyper && !isStylus && ( { !isVyper && !isStylus && (
<Checkbox <Checkbox
size="lg"
mb={ 2 } mb={ 2 }
onChange={ handleCheckboxChange } checked={ isNightly }
isDisabled={ formState.isSubmitting } onCheckedChange={ handleCheckboxChange }
disabled={ formState.isSubmitting }
> >
Include nightly builds Include nightly builds
</Checkbox> </Checkbox>
) } ) }
<FormFieldFancySelect<FormFields, 'compiler'> <FormFieldSelect<FormFields, 'compiler'>
name="compiler" name="compiler"
placeholder="Compiler (enter version or use the dropdown)" placeholder="Compiler (enter version or use the dropdown)"
loadOptions={ loadOptions } collection={ collection }
defaultOptions required
placeholderIcon={ <IconSvg name="search"/> }
isRequired
isAsync
/> />
</> </>
{ isVyper || isStylus ? null : ( { isVyper || isStylus ? null : (
......
import { Link } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { FormFields } from '../types'; import type { FormFields } from '../types';
import { Link } from 'toolkit/chakra/link';
import FormFieldText from 'ui/shared/forms/fields/FormFieldText'; import FormFieldText from 'ui/shared/forms/fields/FormFieldText';
import ContractVerificationFormRow from '../ContractVerificationFormRow'; import ContractVerificationFormRow from '../ContractVerificationFormRow';
...@@ -12,10 +12,9 @@ const ContractVerificationFieldConstructorArgs = () => { ...@@ -12,10 +12,9 @@ const ContractVerificationFieldConstructorArgs = () => {
<ContractVerificationFormRow> <ContractVerificationFormRow>
<FormFieldText<FormFields> <FormFieldText<FormFields>
name="constructor_args" name="constructor_args"
isRequired required
rules={{ maxLength: 255 }} rules={{ maxLength: 255 }}
placeholder="ABI-encoded Constructor Arguments" placeholder="ABI-encoded Constructor Arguments"
size={{ base: 'md', lg: 'lg' }}
asComponent="Textarea" asComponent="Textarea"
/> />
<> <>
......
import { Link } from '@chakra-ui/react'; import { createListCollection } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import React from 'react'; import React from 'react';
...@@ -6,7 +6,8 @@ import type { FormFields } from '../types'; ...@@ -6,7 +6,8 @@ import type { FormFields } from '../types';
import type { SmartContractVerificationConfig } from 'types/client/contract'; import type { SmartContractVerificationConfig } from 'types/client/contract';
import { getResourceKey } from 'lib/api/useApiQuery'; import { getResourceKey } from 'lib/api/useApiQuery';
import FormFieldFancySelect from 'ui/shared/forms/fields/FormFieldFancySelect'; import { Link } from 'toolkit/chakra/link';
import FormFieldSelect from 'ui/shared/forms/fields/FormFieldSelect';
import ContractVerificationFormRow from '../ContractVerificationFormRow'; import ContractVerificationFormRow from '../ContractVerificationFormRow';
...@@ -18,17 +19,19 @@ const ContractVerificationFieldEvmVersion = ({ isVyper }: Props) => { ...@@ -18,17 +19,19 @@ const ContractVerificationFieldEvmVersion = ({ isVyper }: Props) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const config = queryClient.getQueryData<SmartContractVerificationConfig>(getResourceKey('contract_verification_config')); const config = queryClient.getQueryData<SmartContractVerificationConfig>(getResourceKey('contract_verification_config'));
const options = React.useMemo(() => ( const collection = React.useMemo(() => {
(isVyper ? config?.vyper_evm_versions : config?.solidity_evm_versions)?.map((option) => ({ label: option, value: option })) || [] const items = (isVyper ? config?.vyper_evm_versions : config?.solidity_evm_versions)?.map((option) => ({ label: option, value: option })) || [];
), [ config?.solidity_evm_versions, config?.vyper_evm_versions, isVyper ]);
return createListCollection({ items });
}, [ config?.solidity_evm_versions, config?.vyper_evm_versions, isVyper ]);
return ( return (
<ContractVerificationFormRow> <ContractVerificationFormRow>
<FormFieldFancySelect<FormFields, 'evm_version'> <FormFieldSelect<FormFields, 'evm_version'>
name="evm_version" name="evm_version"
placeholder="EVM Version" placeholder="EVM Version"
options={ options } collection={ collection }
isRequired required
/> />
<> <>
<span>The EVM version the contract is written for. If the bytecode does not match the version, we try to verify using the latest EVM version. </span> <span>The EVM version the contract is written for. If the bytecode does not match the version, we try to verify using the latest EVM version. </span>
......
import { Checkbox, useUpdateEffect } from '@chakra-ui/react'; import { useUpdateEffect } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { useFieldArray, useFormContext } from 'react-hook-form'; import { useFieldArray, useFormContext } from 'react-hook-form';
import type { FormFields } from '../types'; import type { FormFields } from '../types';
import { Checkbox } from 'toolkit/chakra/checkbox';
import ContractVerificationFormRow from '../ContractVerificationFormRow'; import ContractVerificationFormRow from '../ContractVerificationFormRow';
import ContractVerificationFieldLibraryItem from './ContractVerificationFieldLibraryItem'; import ContractVerificationFieldLibraryItem from './ContractVerificationFieldLibraryItem';
...@@ -44,10 +46,9 @@ const ContractVerificationFieldLibraries = () => { ...@@ -44,10 +46,9 @@ const ContractVerificationFieldLibraries = () => {
<> <>
<ContractVerificationFormRow> <ContractVerificationFormRow>
<Checkbox <Checkbox
size="lg"
onChange={ handleCheckboxChange } onChange={ handleCheckboxChange }
mt={ 9 } mt={ 9 }
isDisabled={ formState.isSubmitting } disabled={ formState.isSubmitting }
> >
Add contract libraries Add contract libraries
</Checkbox> </Checkbox>
......
import { Flex, IconButton, Text } from '@chakra-ui/react'; import { Flex, Text } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { FormFields } from '../types'; import type { FormFields } from '../types';
import { IconButton } from 'toolkit/chakra/icon-button';
import FormFieldAddress from 'ui/shared/forms/fields/FormFieldAddress'; import FormFieldAddress from 'ui/shared/forms/fields/FormFieldAddress';
import FormFieldText from 'ui/shared/forms/fields/FormFieldText'; import FormFieldText from 'ui/shared/forms/fields/FormFieldText';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
...@@ -38,7 +39,7 @@ const ContractVerificationFieldLibraryItem = ({ index, fieldsLength, onAddFieldC ...@@ -38,7 +39,7 @@ const ContractVerificationFieldLibraryItem = ({ index, fieldsLength, onAddFieldC
<> <>
<ContractVerificationFormRow> <ContractVerificationFormRow>
<Flex alignItems="center" justifyContent="space-between" ref={ ref } mt={ index !== 0 ? 6 : 0 }> <Flex alignItems="center" justifyContent="space-between" ref={ ref } mt={ index !== 0 ? 6 : 0 }>
<Text variant="secondary" fontSize="sm">Contract library { index + 1 }</Text> <Text color="text.secondary" fontSize="sm">Contract library { index + 1 }</Text>
<Flex columnGap={ 5 }> <Flex columnGap={ 5 }>
{ fieldsLength > 1 && ( { fieldsLength > 1 && (
<IconButton <IconButton
...@@ -47,9 +48,10 @@ const ContractVerificationFieldLibraryItem = ({ index, fieldsLength, onAddFieldC ...@@ -47,9 +48,10 @@ const ContractVerificationFieldLibraryItem = ({ index, fieldsLength, onAddFieldC
w="30px" w="30px"
h="30px" h="30px"
onClick={ handleRemoveButtonClick } onClick={ handleRemoveButtonClick }
icon={ <IconSvg name="minus" w="20px" h="20px"/> } disabled={ isDisabled }
isDisabled={ isDisabled } >
/> <IconSvg name="minus" w="20px" h="20px"/>
</IconButton>
) } ) }
{ fieldsLength < LIMIT && ( { fieldsLength < LIMIT && (
<IconButton <IconButton
...@@ -58,9 +60,10 @@ const ContractVerificationFieldLibraryItem = ({ index, fieldsLength, onAddFieldC ...@@ -58,9 +60,10 @@ const ContractVerificationFieldLibraryItem = ({ index, fieldsLength, onAddFieldC
w="30px" w="30px"
h="30px" h="30px"
onClick={ handleAddButtonClick } onClick={ handleAddButtonClick }
icon={ <IconSvg name="plus" w="20px" h="20px"/> } disabled={ isDisabled }
isDisabled={ isDisabled } >
/> <IconSvg name="plus" w="20px" h="20px"/>
</IconButton>
) } ) }
</Flex> </Flex>
</Flex> </Flex>
...@@ -68,10 +71,9 @@ const ContractVerificationFieldLibraryItem = ({ index, fieldsLength, onAddFieldC ...@@ -68,10 +71,9 @@ const ContractVerificationFieldLibraryItem = ({ index, fieldsLength, onAddFieldC
<ContractVerificationFormRow> <ContractVerificationFormRow>
<FormFieldText<FormFields, `libraries.${ number }.name`> <FormFieldText<FormFields, `libraries.${ number }.name`>
name={ `libraries.${ index }.name` } name={ `libraries.${ index }.name` }
isRequired required
rules={{ maxLength: 255 }} rules={{ maxLength: 255 }}
placeholder="Library name (.sol file)" placeholder="Library name (.sol file)"
size={{ base: 'md', lg: 'lg' }}
/> />
{ index === 0 ? ( { index === 0 ? (
<> <>
...@@ -80,11 +82,10 @@ const ContractVerificationFieldLibraryItem = ({ index, fieldsLength, onAddFieldC ...@@ -80,11 +82,10 @@ const ContractVerificationFieldLibraryItem = ({ index, fieldsLength, onAddFieldC
) : null } ) : null }
</ContractVerificationFormRow> </ContractVerificationFormRow>
<ContractVerificationFormRow> <ContractVerificationFormRow>
<FormFieldAddress<FormFields, `libraries.${ number }.address`> <FormFieldAddress<FormFields>
name={ `libraries.${ index }.address` } name={ `libraries.${ index }.address` }
isRequired required
placeholder="Library address (0x...)" placeholder="Library address (0x...)"
size={{ base: 'md', lg: 'lg' }}
/> />
{ index === 0 ? ( { index === 0 ? (
<> <>
......
import { createListCollection } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { FormFields } from '../types'; import type { FormFields } from '../types';
import { CONTRACT_LICENSES } from 'lib/contracts/licenses'; import { CONTRACT_LICENSES } from 'lib/contracts/licenses';
import FormFieldFancySelect from 'ui/shared/forms/fields/FormFieldFancySelect'; import FormFieldSelect from 'ui/shared/forms/fields/FormFieldSelect';
import ContractVerificationFormRow from '../ContractVerificationFormRow'; import ContractVerificationFormRow from '../ContractVerificationFormRow';
const options = CONTRACT_LICENSES.map(({ label, title, type }) => ({ label: `${ title } (${ label })`, value: type })); const collection = createListCollection({
items: CONTRACT_LICENSES.map(({ label, title, type }) => ({ label: `${ title } (${ label })`, value: type })),
});
const ContractVerificationFieldLicenseType = () => { const ContractVerificationFieldLicenseType = () => {
return ( return (
<ContractVerificationFormRow> <ContractVerificationFormRow>
<FormFieldFancySelect<FormFields, 'license_type'> <FormFieldSelect<FormFields, 'license_type'>
name="license_type" name="license_type"
placeholder="Contract license" placeholder="Contract license"
options={ options } collection={ collection }
/> />
<span> <span>
For best practices, all contract source code holders, publishers and authors are encouraged to also For best practices, all contract source code holders, publishers and authors are encouraged to also
......
import { import {
Link,
chakra, chakra,
PopoverTrigger, List,
Portal,
PopoverContent,
PopoverArrow,
PopoverBody,
useColorModeValue,
DarkMode,
ListItem,
OrderedList,
Box, Box,
createListCollection,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { FormFields } from '../types'; import type { FormFields } from '../types';
import type { SmartContractVerificationMethod, SmartContractVerificationConfig } from 'types/client/contract'; import type { SmartContractVerificationMethod, SmartContractVerificationConfig } from 'types/client/contract';
import useIsMobile from 'lib/hooks/useIsMobile'; import { Link } from 'toolkit/chakra/link';
import Popover from 'ui/shared/chakra/Popover'; import { Tooltip } from 'toolkit/chakra/tooltip';
import FormFieldFancySelect from 'ui/shared/forms/fields/FormFieldFancySelect'; import FormFieldSelect from 'ui/shared/forms/fields/FormFieldSelect';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import { METHOD_LABELS } from '../utils'; import { METHOD_LABELS } from '../utils';
...@@ -29,95 +21,87 @@ interface Props { ...@@ -29,95 +21,87 @@ interface Props {
} }
const ContractVerificationFieldMethod = ({ methods }: Props) => { const ContractVerificationFieldMethod = ({ methods }: Props) => {
const tooltipBg = useColorModeValue('gray.700', 'gray.900'); const collection = React.useMemo(() => createListCollection({
const isMobile = useIsMobile(); items: methods.map((method) => ({
const options = React.useMemo(() => methods.map((method) => ({
value: method, value: method,
label: METHOD_LABELS[method], label: METHOD_LABELS[method],
})), [ methods ]); })),
}), [ methods ]);
const renderPopoverListItem = React.useCallback((method: SmartContractVerificationMethod) => { const renderPopoverListItem = React.useCallback((method: SmartContractVerificationMethod) => {
switch (method) { switch (method) {
case 'flattened-code': case 'flattened-code':
return <ListItem key={ method }>Verification through a single file.</ListItem>; return <List.Item key={ method }>Verification through a single file.</List.Item>;
case 'multi-part': case 'multi-part':
return <ListItem key={ method }>Verification of multi-part Solidity files.</ListItem>; return <List.Item key={ method }>Verification of multi-part Solidity files.</List.Item>;
case 'sourcify': case 'sourcify':
return <ListItem key={ method }>Verification through <Link href="https://sourcify.dev/" target="_blank">Sourcify</Link>.</ListItem>; return <List.Item key={ method }>Verification through <Link href="https://sourcify.dev/" target="_blank" className="dark">Sourcify</Link>.</List.Item>;
case 'standard-input': case 'standard-input':
return ( return (
<ListItem key={ method }> <List.Item key={ method }>
<span>Verification using </span> <span>Verification using </span>
<Link <Link
href="https://docs.soliditylang.org/en/latest/using-the-compiler.html#input-description" href="https://docs.soliditylang.org/en/latest/using-the-compiler.html#input-description"
target="_blank" target="_blank"
className="dark"
> >
Standard input JSON Standard input JSON
</Link> </Link>
<span> file.</span> <span> file.</span>
</ListItem> </List.Item>
); );
case 'vyper-code': case 'vyper-code':
return <ListItem key={ method }>Verification of Vyper contract.</ListItem>; return <List.Item key={ method }>Verification of Vyper contract.</List.Item>;
case 'vyper-multi-part': case 'vyper-multi-part':
return <ListItem key={ method }>Verification of multi-part Vyper files.</ListItem>; return <List.Item key={ method }>Verification of multi-part Vyper files.</List.Item>;
case 'vyper-standard-input': case 'vyper-standard-input':
return ( return (
<ListItem key={ method }> <List.Item key={ method }>
<span>Verification of Vyper contract using </span> <span>Verification of Vyper contract using </span>
<Link <Link
href="https://docs.vyperlang.org/en/stable/compiling-a-contract.html#compiler-input-and-output-json-description" href="https://docs.vyperlang.org/en/stable/compiling-a-contract.html#compiler-input-and-output-json-description"
target="_blank" target="_blank"
className="dark"
> >
Standard input JSON Standard input JSON
</Link> </Link>
<span> file.</span> <span> file.</span>
</ListItem> </List.Item>
); );
case 'solidity-hardhat': case 'solidity-hardhat':
return <ListItem key={ method }>Verification through Hardhat plugin.</ListItem>; return <List.Item key={ method }>Verification through Hardhat plugin.</List.Item>;
case 'solidity-foundry': case 'solidity-foundry':
return <ListItem key={ method }>Verification through Foundry.</ListItem>; return <List.Item key={ method }>Verification through Foundry.</List.Item>;
case 'stylus-github-repository': case 'stylus-github-repository':
return <ListItem key={ method }>Verification of Stylus contract via GitHub repository.</ListItem>; return <List.Item key={ method }>Verification of Stylus contract via GitHub repository.</List.Item>;
} }
}, []); }, []);
const tooltipContent = (
<Box>
<span>Currently, Blockscout supports { methods.length } methods:</span>
<List.Root as="ol" pl={ 5 }>
{ methods.map(renderPopoverListItem) }
</List.Root>
</Box>
);
return ( return (
<> <>
<Box mt={{ base: 10, lg: 6 }} gridColumn={{ lg: '1 / 3' }}> <Box mt={{ base: 10, lg: 6 }} gridColumn={{ lg: '1 / 3' }}>
<chakra.span fontWeight={ 500 } fontSize="lg" fontFamily="heading"> <chakra.span fontWeight={ 500 } fontSize="lg" fontFamily="heading">
Currently, Blockscout supports { methods.length } contract verification methods Currently, Blockscout supports { methods.length } contract verification methods
</chakra.span> </chakra.span>
<Popover trigger="hover" isLazy placement={ isMobile ? 'bottom-end' : 'right-start' } offset={ [ -8, 8 ] }> <Tooltip content={ tooltipContent } interactive contentProps={{ textAlign: 'left', className: 'light' }}>
<PopoverTrigger> <IconSvg name="info" boxSize={ 5 } ml={ 1 } cursor="pointer" color="icon.info" _hover={{ color: 'link.primary.hover' }}/>
<chakra.span display="inline-block" ml={ 1 } cursor="pointer" verticalAlign="middle" h="22px"> </Tooltip>
<IconSvg name="info" boxSize={ 5 } color="icon_info" _hover={{ color: 'link_hovered' }}/>
</chakra.span>
</PopoverTrigger>
<Portal>
<PopoverContent bgColor={ tooltipBg } w={{ base: '300px', lg: '380px' }}>
<PopoverArrow bgColor={ tooltipBg }/>
<PopoverBody color="white">
<DarkMode>
<span>Currently, Blockscout supports { methods.length } methods:</span>
<OrderedList>
{ methods.map(renderPopoverListItem) }
</OrderedList>
</DarkMode>
</PopoverBody>
</PopoverContent>
</Portal>
</Popover>
</Box> </Box>
<FormFieldFancySelect<FormFields, 'method'> <FormFieldSelect<FormFields, 'method'>
name="method" name="method"
placeholder="Verification method (compiler type)" placeholder="Verification method (compiler type)"
options={ options } collection={ collection }
isRequired required
isAsync={ false } readOnly={ collection.items.length === 1 }
isReadOnly={ options.length === 1 }
/> />
</> </>
); );
......
...@@ -16,9 +16,8 @@ const ContractVerificationFieldName = ({ hint }: Props) => { ...@@ -16,9 +16,8 @@ const ContractVerificationFieldName = ({ hint }: Props) => {
<ContractVerificationFormRow> <ContractVerificationFormRow>
<FormFieldText<FormFields> <FormFieldText<FormFields>
name="name" name="name"
isRequired required
placeholder="Contract name" placeholder="Contract name"
size={{ base: 'md', lg: 'lg' }}
rules={{ maxLength: 255 }} rules={{ maxLength: 255 }}
/> />
{ hint ? <span>{ hint }</span> : ( { hint ? <span>{ hint }</span> : (
......
...@@ -27,10 +27,12 @@ const ContractVerificationFieldOptimization = () => { ...@@ -27,10 +27,12 @@ const ContractVerificationFieldOptimization = () => {
{ isEnabled && ( { isEnabled && (
<FormFieldText<FormFields, 'optimization_runs'> <FormFieldText<FormFields, 'optimization_runs'>
name="optimization_runs" name="optimization_runs"
isRequired required
placeholder="Optimization runs" placeholder="Optimization runs"
type="number" inputProps={{
size="xs" type: 'number',
}}
size="sm"
minW="100px" minW="100px"
maxW="200px" maxW="200px"
flexShrink={ 1 } flexShrink={ 1 }
......
...@@ -7,11 +7,6 @@ export interface ContractLibrary { ...@@ -7,11 +7,6 @@ export interface ContractLibrary {
address: string; address: string;
} }
interface MethodOption {
label: string;
value: SmartContractVerificationMethod;
}
export interface LicenseOption { export interface LicenseOption {
label: string; label: string;
value: SmartContractLicenseType; value: SmartContractLicenseType;
...@@ -19,15 +14,15 @@ export interface LicenseOption { ...@@ -19,15 +14,15 @@ export interface LicenseOption {
interface FormFieldsBase { interface FormFieldsBase {
address: string; address: string;
method: MethodOption; method: Array<SmartContractVerificationMethod>;
license_type: LicenseOption | null; license_type: Array<SmartContractLicenseType>;
} }
export interface FormFieldsFlattenSourceCode extends FormFieldsBase { export interface FormFieldsFlattenSourceCode extends FormFieldsBase {
is_yul: boolean; is_yul: boolean;
name: string | undefined; name: string | undefined;
compiler: Option | null; compiler: Array<string>;
evm_version: Option | null; evm_version: Array<string>;
is_optimization_enabled: boolean; is_optimization_enabled: boolean;
optimization_runs: string; optimization_runs: string;
code: string; code: string;
...@@ -38,7 +33,7 @@ export interface FormFieldsFlattenSourceCode extends FormFieldsBase { ...@@ -38,7 +33,7 @@ export interface FormFieldsFlattenSourceCode extends FormFieldsBase {
export interface FormFieldsStandardInput extends FormFieldsBase { export interface FormFieldsStandardInput extends FormFieldsBase {
name: string; name: string;
compiler: Option | null; compiler: Array<string>;
sources: Array<File>; sources: Array<File>;
autodetect_constructor_args: boolean; autodetect_constructor_args: boolean;
constructor_args: string; constructor_args: string;
...@@ -46,8 +41,8 @@ export interface FormFieldsStandardInput extends FormFieldsBase { ...@@ -46,8 +41,8 @@ export interface FormFieldsStandardInput extends FormFieldsBase {
export interface FormFieldsStandardInputZk extends FormFieldsBase { export interface FormFieldsStandardInputZk extends FormFieldsBase {
name: string; name: string;
compiler: Option | null; compiler: Array<string>;
zk_compiler: Option | null; zk_compiler: Array<string>;
sources: Array<File>; sources: Array<File>;
autodetect_constructor_args: boolean; autodetect_constructor_args: boolean;
constructor_args: string; constructor_args: string;
...@@ -59,8 +54,8 @@ export interface FormFieldsSourcify extends FormFieldsBase { ...@@ -59,8 +54,8 @@ export interface FormFieldsSourcify extends FormFieldsBase {
} }
export interface FormFieldsMultiPartFile extends FormFieldsBase { export interface FormFieldsMultiPartFile extends FormFieldsBase {
compiler: Option | null; compiler: Array<string>;
evm_version: Option | null; evm_version: Array<string>;
is_optimization_enabled: boolean; is_optimization_enabled: boolean;
optimization_runs: string; optimization_runs: string;
sources: Array<File>; sources: Array<File>;
...@@ -69,26 +64,26 @@ export interface FormFieldsMultiPartFile extends FormFieldsBase { ...@@ -69,26 +64,26 @@ export interface FormFieldsMultiPartFile extends FormFieldsBase {
export interface FormFieldsVyperContract extends FormFieldsBase { export interface FormFieldsVyperContract extends FormFieldsBase {
name: string; name: string;
evm_version: Option | null; evm_version: Array<string>;
compiler: Option | null; compiler: Array<string>;
code: string; code: string;
constructor_args: string | undefined; constructor_args: string | undefined;
} }
export interface FormFieldsVyperMultiPartFile extends FormFieldsBase { export interface FormFieldsVyperMultiPartFile extends FormFieldsBase {
compiler: Option | null; compiler: Array<string>;
evm_version: Option | null; evm_version: Array<string>;
sources: Array<File>; sources: Array<File>;
interfaces: Array<File>; interfaces: Array<File>;
} }
export interface FormFieldsVyperStandardInput extends FormFieldsBase { export interface FormFieldsVyperStandardInput extends FormFieldsBase {
compiler: Option | null; compiler: Array<string>;
sources: Array<File>; sources: Array<File>;
} }
export interface FormFieldsStylusGitHubRepo extends FormFieldsBase { export interface FormFieldsStylusGitHubRepo extends FormFieldsBase {
compiler: Option | null; compiler: Array<string>;
repository_url: string; repository_url: string;
commit_hash: string; commit_hash: string;
path_prefix: string; path_prefix: string;
......
...@@ -51,123 +51,93 @@ export const METHOD_LABELS: Record<SmartContractVerificationMethod, string> = { ...@@ -51,123 +51,93 @@ export const METHOD_LABELS: Record<SmartContractVerificationMethod, string> = {
export const DEFAULT_VALUES: Record<SmartContractVerificationMethod, FormFields> = { export const DEFAULT_VALUES: Record<SmartContractVerificationMethod, FormFields> = {
'flattened-code': { 'flattened-code': {
address: '', address: '',
method: { method: [ 'flattened-code' ],
value: 'flattened-code' as const,
label: METHOD_LABELS['flattened-code'],
},
is_yul: false, is_yul: false,
name: '', name: '',
compiler: null, compiler: [],
evm_version: null, evm_version: [],
is_optimization_enabled: true, is_optimization_enabled: true,
optimization_runs: '200', optimization_runs: '200',
code: '', code: '',
autodetect_constructor_args: true, autodetect_constructor_args: true,
constructor_args: '', constructor_args: '',
libraries: [], libraries: [],
license_type: null, license_type: [],
}, },
'standard-input': { 'standard-input': {
address: '', address: '',
method: { method: [ 'standard-input' ],
value: 'standard-input' as const,
label: METHOD_LABELS['standard-input'],
},
name: '', name: '',
compiler: null, compiler: [],
sources: [], sources: [],
autodetect_constructor_args: true, autodetect_constructor_args: true,
constructor_args: '', constructor_args: '',
license_type: null, license_type: [],
}, },
sourcify: { sourcify: {
address: '', address: '',
method: { method: [ 'sourcify' ],
value: 'sourcify' as const,
label: METHOD_LABELS.sourcify,
},
sources: [], sources: [],
license_type: null, license_type: [],
}, },
'multi-part': { 'multi-part': {
address: '', address: '',
method: { method: [ 'multi-part' ],
value: 'multi-part' as const, compiler: [],
label: METHOD_LABELS['multi-part'], evm_version: [],
},
compiler: null,
evm_version: null,
is_optimization_enabled: true, is_optimization_enabled: true,
optimization_runs: '200', optimization_runs: '200',
sources: [], sources: [],
libraries: [], libraries: [],
license_type: null, license_type: [],
}, },
'vyper-code': { 'vyper-code': {
address: '', address: '',
method: { method: [ 'vyper-code' ],
value: 'vyper-code' as const,
label: METHOD_LABELS['vyper-code'],
},
name: '', name: '',
compiler: null, compiler: [],
evm_version: null, evm_version: [],
code: '', code: '',
constructor_args: '', constructor_args: '',
license_type: null, license_type: [],
}, },
'vyper-multi-part': { 'vyper-multi-part': {
address: '', address: '',
method: { method: [ 'vyper-multi-part' ],
value: 'vyper-multi-part' as const, compiler: [],
label: METHOD_LABELS['vyper-multi-part'], evm_version: [],
},
compiler: null,
evm_version: null,
sources: [], sources: [],
license_type: null, license_type: [],
}, },
'vyper-standard-input': { 'vyper-standard-input': {
address: '', address: '',
method: { method: [ 'vyper-standard-input' ],
value: 'vyper-standard-input' as const, compiler: [],
label: METHOD_LABELS['vyper-standard-input'],
},
compiler: null,
sources: [], sources: [],
license_type: null, license_type: [],
}, },
'solidity-hardhat': { 'solidity-hardhat': {
address: '', address: '',
method: { method: [ 'solidity-hardhat' ],
value: 'solidity-hardhat' as const, compiler: [],
label: METHOD_LABELS['solidity-hardhat'],
},
compiler: null,
sources: [], sources: [],
license_type: null, license_type: [],
}, },
'solidity-foundry': { 'solidity-foundry': {
address: '', address: '',
method: { method: [ 'solidity-foundry' ],
value: 'solidity-foundry' as const, compiler: [],
label: METHOD_LABELS['solidity-foundry'],
},
compiler: null,
sources: [], sources: [],
license_type: null, license_type: [],
}, },
'stylus-github-repository': { 'stylus-github-repository': {
address: '', address: '',
method: { method: [ 'stylus-github-repository' ],
value: 'stylus-github-repository' as const, compiler: [],
label: METHOD_LABELS['stylus-github-repository'],
},
compiler: null,
repository_url: '', repository_url: '',
commit_hash: '', commit_hash: '',
path_prefix: '', path_prefix: '',
license_type: null, license_type: [],
}, },
}; };
...@@ -188,11 +158,11 @@ export function getDefaultValues( ...@@ -188,11 +158,11 @@ export function getDefaultValues(
if ('evm_version' in defaultValues) { if ('evm_version' in defaultValues) {
if (method === 'flattened-code' || method === 'multi-part') { if (method === 'flattened-code' || method === 'multi-part') {
defaultValues.evm_version = config.solidity_evm_versions.find((value) => value === 'default') ? { label: 'default', value: 'default' } : null; defaultValues.evm_version = config.solidity_evm_versions.find((value) => value === 'default') ? [ 'default' ] : [];
} }
if (method === 'vyper-multi-part') { if (method === 'vyper-multi-part') {
defaultValues.evm_version = config.vyper_evm_versions.find((value) => value === 'default') ? { label: 'default', value: 'default' } : null; defaultValues.evm_version = config.vyper_evm_versions.find((value) => value === 'default') ? [ 'default' ] : [];
} }
} }
...@@ -204,10 +174,7 @@ export function getDefaultValues( ...@@ -204,10 +174,7 @@ export function getDefaultValues(
} }
if (singleMethod) { if (singleMethod) {
defaultValues.method = { defaultValues.method = config.verification_options;
label: METHOD_LABELS[config.verification_options[0]],
value: config.verification_options[0],
};
} }
return defaultValues; return defaultValues;
...@@ -235,21 +202,21 @@ export function sortVerificationMethods(methodA: SmartContractVerificationMethod ...@@ -235,21 +202,21 @@ export function sortVerificationMethods(methodA: SmartContractVerificationMethod
export function prepareRequestBody(data: FormFields): FetchParams['body'] { export function prepareRequestBody(data: FormFields): FetchParams['body'] {
const defaultLicenseType: SmartContractLicenseType = 'none'; const defaultLicenseType: SmartContractLicenseType = 'none';
switch (data.method.value) { switch (data.method[0]) {
case 'flattened-code': { case 'flattened-code': {
const _data = data as FormFieldsFlattenSourceCode; const _data = data as FormFieldsFlattenSourceCode;
return { return {
compiler_version: _data.compiler?.value, compiler_version: _data.compiler?.[0],
source_code: _data.code, source_code: _data.code,
is_optimization_enabled: _data.is_optimization_enabled, is_optimization_enabled: _data.is_optimization_enabled,
is_yul_contract: _data.is_yul, is_yul_contract: _data.is_yul,
optimization_runs: _data.optimization_runs, optimization_runs: _data.optimization_runs,
contract_name: _data.name || undefined, contract_name: _data.name || undefined,
libraries: reduceLibrariesArray(_data.libraries), libraries: reduceLibrariesArray(_data.libraries),
evm_version: _data.evm_version?.value, evm_version: _data.evm_version?.[0],
autodetect_constructor_args: _data.autodetect_constructor_args, autodetect_constructor_args: _data.autodetect_constructor_args,
constructor_args: _data.constructor_args, constructor_args: _data.constructor_args,
license_type: _data.license_type?.value ?? defaultLicenseType, license_type: _data.license_type?.[0] ?? defaultLicenseType,
}; };
} }
...@@ -257,15 +224,15 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] { ...@@ -257,15 +224,15 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
const _data = data as (FormFieldsStandardInput | FormFieldsStandardInputZk); const _data = data as (FormFieldsStandardInput | FormFieldsStandardInputZk);
const body = new FormData(); const body = new FormData();
_data.compiler && body.set('compiler_version', _data.compiler.value); _data.compiler && body.set('compiler_version', _data.compiler?.[0]);
body.set('license_type', _data.license_type?.value ?? defaultLicenseType); body.set('license_type', _data.license_type?.[0] ?? defaultLicenseType);
body.set('contract_name', _data.name); body.set('contract_name', _data.name);
body.set('autodetect_constructor_args', String(Boolean(_data.autodetect_constructor_args))); body.set('autodetect_constructor_args', String(Boolean(_data.autodetect_constructor_args)));
body.set('constructor_args', _data.constructor_args); body.set('constructor_args', _data.constructor_args);
addFilesToFormData(body, _data.sources, 'files'); addFilesToFormData(body, _data.sources, 'files');
// zkSync fields // zkSync fields
'zk_compiler' in _data && _data.zk_compiler && body.set('zk_compiler_version', _data.zk_compiler.value); 'zk_compiler' in _data && _data.zk_compiler && body.set('zk_compiler_version', _data.zk_compiler?.[0]);
return body; return body;
} }
...@@ -274,8 +241,8 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] { ...@@ -274,8 +241,8 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
const _data = data as FormFieldsSourcify; const _data = data as FormFieldsSourcify;
const body = new FormData(); const body = new FormData();
addFilesToFormData(body, _data.sources, 'files'); addFilesToFormData(body, _data.sources, 'files');
body.set('chosen_contract_index', _data.contract_index?.value ?? defaultLicenseType); body.set('chosen_contract_index', _data.contract_index?.value ?? '0');
_data.license_type && body.set('license_type', _data.license_type.value); _data.license_type && body.set('license_type', _data.license_type?.[0] ?? defaultLicenseType);
return body; return body;
} }
...@@ -284,9 +251,9 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] { ...@@ -284,9 +251,9 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
const _data = data as FormFieldsMultiPartFile; const _data = data as FormFieldsMultiPartFile;
const body = new FormData(); const body = new FormData();
_data.compiler && body.set('compiler_version', _data.compiler.value); _data.compiler && body.set('compiler_version', _data.compiler?.[0]);
_data.evm_version && body.set('evm_version', _data.evm_version.value); _data.evm_version && body.set('evm_version', _data.evm_version?.[0]);
body.set('license_type', _data.license_type?.value ?? defaultLicenseType); body.set('license_type', _data.license_type?.[0] ?? defaultLicenseType);
body.set('is_optimization_enabled', String(Boolean(_data.is_optimization_enabled))); body.set('is_optimization_enabled', String(Boolean(_data.is_optimization_enabled)));
_data.is_optimization_enabled && body.set('optimization_runs', _data.optimization_runs); _data.is_optimization_enabled && body.set('optimization_runs', _data.optimization_runs);
...@@ -301,12 +268,12 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] { ...@@ -301,12 +268,12 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
const _data = data as FormFieldsVyperContract; const _data = data as FormFieldsVyperContract;
return { return {
compiler_version: _data.compiler?.value, compiler_version: _data.compiler?.[0],
evm_version: _data.evm_version?.value, evm_version: _data.evm_version?.[0],
source_code: _data.code, source_code: _data.code,
contract_name: _data.name, contract_name: _data.name,
constructor_args: _data.constructor_args, constructor_args: _data.constructor_args,
license_type: _data.license_type?.value ?? defaultLicenseType, license_type: _data.license_type?.[0] ?? defaultLicenseType,
}; };
} }
...@@ -314,9 +281,9 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] { ...@@ -314,9 +281,9 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
const _data = data as FormFieldsVyperMultiPartFile; const _data = data as FormFieldsVyperMultiPartFile;
const body = new FormData(); const body = new FormData();
_data.compiler && body.set('compiler_version', _data.compiler.value); _data.compiler && body.set('compiler_version', _data.compiler?.[0]);
_data.evm_version && body.set('evm_version', _data.evm_version.value); _data.evm_version && body.set('evm_version', _data.evm_version?.[0]);
body.set('license_type', _data.license_type?.value ?? defaultLicenseType); body.set('license_type', _data.license_type?.[0] ?? defaultLicenseType);
addFilesToFormData(body, _data.sources, 'files'); addFilesToFormData(body, _data.sources, 'files');
addFilesToFormData(body, _data.interfaces, 'interfaces'); addFilesToFormData(body, _data.interfaces, 'interfaces');
...@@ -327,8 +294,8 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] { ...@@ -327,8 +294,8 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
const _data = data as FormFieldsVyperStandardInput; const _data = data as FormFieldsVyperStandardInput;
const body = new FormData(); const body = new FormData();
_data.compiler && body.set('compiler_version', _data.compiler.value); _data.compiler && body.set('compiler_version', _data.compiler?.[0]);
body.set('license_type', _data.license_type?.value ?? defaultLicenseType); body.set('license_type', _data.license_type?.[0] ?? defaultLicenseType);
addFilesToFormData(body, _data.sources, 'files'); addFilesToFormData(body, _data.sources, 'files');
return body; return body;
...@@ -338,11 +305,11 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] { ...@@ -338,11 +305,11 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
const _data = data as FormFieldsStylusGitHubRepo; const _data = data as FormFieldsStylusGitHubRepo;
return { return {
cargo_stylus_version: _data.compiler?.value, cargo_stylus_version: _data.compiler?.[0],
repository_url: _data.repository_url, repository_url: _data.repository_url,
commit: _data.commit_hash, commit: _data.commit_hash,
path_prefix: _data.path_prefix, path_prefix: _data.path_prefix,
license_type: _data.license_type?.value ?? defaultLicenseType, license_type: _data.license_type?.[0] ?? defaultLicenseType,
}; };
} }
......
...@@ -93,6 +93,8 @@ const ContractVerificationForAddress = () => { ...@@ -93,6 +93,8 @@ const ContractVerificationForAddress = () => {
fontSize="lg" fontSize="lg"
fontWeight={ 500 } fontWeight={ 500 }
mb={ 12 } mb={ 12 }
w="min-content"
maxW="100%"
/> />
{ content } { content }
</> </>
......
...@@ -207,8 +207,8 @@ const VerifiedAddresses = () => { ...@@ -207,8 +207,8 @@ const VerifiedAddresses = () => {
Before starting, make sure that: Before starting, make sure that:
</chakra.p> </chakra.p>
<List.Root ml={ 6 } as="ol"> <List.Root ml={ 6 } as="ol">
<List.Item _marker={{ color: 'inherit' }}>The source code for the smart contract is deployed on “{ config.chain.name }”.</List.Item> <List.Item>The source code for the smart contract is deployed on “{ config.chain.name }”.</List.Item>
<List.Item _marker={{ color: 'inherit' }}> <List.Item>
<span>The source code is verified (if not yet verified, you can use </span> <span>The source code is verified (if not yet verified, you can use </span>
<Link href="https://docs.blockscout.com/for-users/verifying-a-smart-contract" target="_blank">this tool</Link> <Link href="https://docs.blockscout.com/for-users/verifying-a-smart-contract" target="_blank">this tool</Link>
<span>).</span> <span>).</span>
......
...@@ -31,10 +31,15 @@ const FormFieldText = < ...@@ -31,10 +31,15 @@ const FormFieldText = <
group, group,
inputProps, inputProps,
asComponent, asComponent,
size = asComponent === 'Textarea' ? '2xl' : 'xl', size: sizeProp,
disabled, disabled,
floating: floatingProp,
...restProps ...restProps
}: Props<FormFields, Name>) => { }: Props<FormFields, Name>) => {
const defaultSize = asComponent === 'Textarea' ? '2xl' : 'xl';
const size = sizeProp || defaultSize;
const floating = floatingProp !== undefined ? floatingProp : size === defaultSize;
const { control } = useFormContext<FormFields>(); const { control } = useFormContext<FormFields>();
const { field, fieldState, formState } = useController<FormFields, typeof name>({ const { field, fieldState, formState } = useController<FormFields, typeof name>({
control, control,
...@@ -59,6 +64,8 @@ const FormFieldText = < ...@@ -59,6 +64,8 @@ const FormFieldText = <
<Input <Input
{ ...field } { ...field }
autoComplete="off" autoComplete="off"
// for non-floating field label, we pass placeholder to the input component
placeholder={ !floating ? placeholder : undefined }
{ ...inputProps as InputProps } { ...inputProps as InputProps }
onBlur={ handleBlur } onBlur={ handleBlur }
/> />
...@@ -75,12 +82,13 @@ const FormFieldText = < ...@@ -75,12 +82,13 @@ const FormFieldText = <
return ( return (
<Field <Field
label={ placeholder } // for floating field label, we pass placeholder value to the label
label={ floating ? placeholder : undefined }
errorText={ getFieldErrorText(fieldState.error) } errorText={ getFieldErrorText(fieldState.error) }
invalid={ Boolean(fieldState.error) } invalid={ Boolean(fieldState.error) }
disabled={ formState.isSubmitting || disabled } disabled={ formState.isSubmitting || disabled }
size={ size } size={ size }
floating floating={ floating }
{ ...restProps } { ...restProps }
> >
{ content } { content }
......
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