Commit 8fa9ba9f authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge pull request #543 from blockscout/contract-verification-form

contract verification form
parents 2c7e69d4 9f8cc98c
...@@ -65,6 +65,31 @@ const oldUrls = [ ...@@ -65,6 +65,31 @@ const oldUrls = [
oldPath: '/address/:id/tokens/:hash/token-transfers', oldPath: '/address/:id/tokens/:hash/token-transfers',
newPath: `${ PATHS.address_index }?tab=token_transfers&token=:hash`, newPath: `${ PATHS.address_index }?tab=token_transfers&token=:hash`,
}, },
// contract verification
{
oldPath: '/address/:id/contract_verifications/new',
newPath: `${ PATHS.address_contract_verification }`,
},
{
oldPath: '/address/:id/verify-via-flattened-code/new',
newPath: `${ PATHS.address_contract_verification }?method=flatten_source_code`,
},
{
oldPath: '/address/:id/verify-via-standard-json-input/new',
newPath: `${ PATHS.address_contract_verification }?method=standard_input`,
},
{
oldPath: '/address/:id/verify-via-metadata-json/new',
newPath: `${ PATHS.address_contract_verification }?method=sourcify`,
},
{
oldPath: '/address/:id/verify-via-multi-part-files/new',
newPath: `${ PATHS.address_contract_verification }?method=multi_part_file`,
},
{
oldPath: '/address/:id/verify-vyper-contract/new',
newPath: `${ PATHS.address_contract_verification }?method=vyper_contract`,
},
]; ];
async function redirects() { async function redirects() {
......
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.4 2.933a.156.156 0 0 0-.155.156v10.844a.467.467 0 0 1-.934 0V3.09A1.089 1.089 0 0 1 6.401 2h7.474a.467.467 0 0 1 .322.137l4.355 4.355a.467.467 0 0 1 .137.33v7.111a.467.467 0 0 1-.933 0V7.29h-3.89a.467.467 0 0 1-.466-.467V2.933h-7Zm7.933.66v2.763h2.763l-2.763-2.763Z" fill="currentColor" stroke="currentColor" stroke-width=".4" stroke-linecap="round" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.224 18.185v.628c0 .308-.04.565-.12.771-.067.19-.181.352-.328.465a.794.794 0 0 1-.48.152.8.8 0 0 1-.48-.152 1.026 1.026 0 0 1-.326-.465 2.159 2.159 0 0 1-.12-.77v-.629c0-.31.04-.566.12-.77.068-.19.181-.352.327-.466a.788.788 0 0 1 .48-.155c.18 0 .34.052.479.155.146.113.26.275.329.465.08.205.12.461.12.771Zm.82.625v-.617c0-.454-.07-.844-.21-1.17a1.674 1.674 0 0 0-.602-.758c-.258-.176-.57-.265-.935-.265-.363 0-.675.089-.939.265-.26.173-.47.437-.6.755-.14.326-.21.717-.21 1.173v.616c0 .452.07.841.21 1.17.139.327.339.578.6.754.264.174.576.26.94.26s.676-.086.935-.26c.262-.176.462-.427.601-.753.14-.33.21-.72.21-1.17Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.22 16.056c.31-.206.671-.306 1.077-.306.406 0 .77.1 1.075.308.306.205.545.51.693.868.155.364.229.788.229 1.267v.616c0 .477-.075.901-.23 1.268v.001a1.923 1.923 0 0 1-.691.862c-.307.207-.67.304-1.076.304-.404 0-.766-.097-1.077-.303h-.001a1.923 1.923 0 0 1-.692-.863c-.156-.367-.23-.793-.23-1.268v-.617c0-.48.074-.905.23-1.27a1.9 1.9 0 0 1 .693-.866Zm1.077.194c-.32 0-.583.078-.8.223a1.403 1.403 0 0 0-.509.642l-.001.004c-.123.287-.19.642-.19 1.074v.616c0 .426.066.781.19 1.073.123.287.294.498.51.643.216.143.479.219.8.219.324 0 .586-.077.797-.219.216-.145.387-.355.51-.643.123-.292.19-.647.19-1.073v-.616c0-.429-.067-.784-.19-1.073v-.002a1.425 1.425 0 0 0-.511-.646h-.001c-.21-.144-.472-.222-.795-.222Zm-.33.898a.78.78 0 0 0-.242.35l-.002.007c-.065.167-.103.39-.103.68v.635c-.006.234.03.466.106.68a.78.78 0 0 0 .24.349c.102.07.215.104.325.102h.011c.11.002.223-.031.325-.102a.768.768 0 0 0 .242-.349l.002-.006c.066-.168.103-.392.103-.68v-.629c0-.29-.037-.514-.102-.68l-.003-.007a.767.767 0 0 0-.244-.35.535.535 0 0 0-.329-.104h-.005a.537.537 0 0 0-.324.104Zm.332-.604a1.037 1.037 0 0 0-.63.203l-.007.005a1.276 1.276 0 0 0-.406.575 2.39 2.39 0 0 0-.136.858v.625a2.41 2.41 0 0 0 .134.857v.001c.083.23.223.433.408.578l.01.008c.185.13.4.2.624.197.224.004.44-.066.625-.198l.008-.006c.187-.144.328-.346.41-.576.093-.243.135-.532.135-.858v-.628a2.39 2.39 0 0 0-.135-.858 1.265 1.265 0 0 0-.41-.576l-.004-.003a1.034 1.034 0 0 0-.626-.204Z" fill="currentColor"/>
<path d="M17.06 21v-1h1.301a.5.5 0 0 1 .5.5v.5H17.06Zm-.699 0a.5.5 0 0 1-.5-.5v-3.9a.6.6 0 0 1 1.199 0V21h-.699Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.861 21v-.5a.5.5 0 0 0-.5-.5H17.06v-3.4a.6.6 0 0 0-1.199 0v3.9a.5.5 0 0 0 .5.5h2.5Zm-1.701-1.1v-3.3a.7.7 0 0 0-1.399 0v3.9a.6.6 0 0 0 .6.6h2.6v-.6a.6.6 0 0 0-.6-.6H17.16Z" fill="currentColor"/>
<path d="M6.468 17.532c0-.192.067-.316.163-.398.104-.09.286-.166.562-.166.332 0 .655.108.921.307a.484.484 0 0 0 .579-.776A2.516 2.516 0 0 0 7.195 16h-.001c-.45 0-.873.125-1.192.399-.328.28-.502.68-.502 1.133 0 .244.067.464.196.651.124.182.29.309.452.401.29.165.657.262.949.34l.053.013c.339.09.589.163.76.267.135.083.17.15.17.264 0 .25-.086.352-.19.419-.138.089-.372.145-.696.145a1.548 1.548 0 0 1-.92-.307.484.484 0 0 0-.58.776c.433.322.958.498 1.498.499h.002c.402 0 .853-.064 1.218-.298.4-.256.636-.677.636-1.234 0-.532-.287-.878-.635-1.09-.31-.189-.699-.292-1.003-.373l-.012-.003c-.344-.091-.597-.16-.772-.26a.39.39 0 0 1-.132-.106c-.013-.018-.026-.045-.026-.104Z" fill="currentColor" stroke="currentColor" stroke-width=".4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.4 2.933a.156.156 0 0 0-.155.156v10.844a.467.467 0 0 1-.934 0V3.09A1.089 1.089 0 0 1 6.401 2h7.474a.467.467 0 0 1 .322.137l4.355 4.355a.467.467 0 0 1 .137.33v7.111a.467.467 0 0 1-.933 0V7.29h-3.89a.467.467 0 0 1-.466-.467V2.933h-7Zm7.933.66v2.763h2.763l-2.763-2.763Z" fill="currentColor" stroke="currentColor" stroke-width=".4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M17.06 20h1.301a.5.5 0 0 1 .5.5v.5h-2.5a.5.5 0 0 1-.5-.5v-3.9a.6.6 0 0 1 1.199 0V20Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.861 21v-.5a.5.5 0 0 0-.5-.5H17.06v-3.4a.6.6 0 0 0-1.199 0v3.9a.5.5 0 0 0 .5.5h2.5Zm-1.701-1.1v-3.3a.7.7 0 0 0-1.399 0v3.9a.6.6 0 0 0 .6.6h2.6v-.6a.6.6 0 0 0-.6-.6H17.16Z" fill="currentColor"/>
<path d="M11.18 15.85a.63.63 0 0 1 .63.63v3.131c0 .177.043.291.11.365.066.071.156.113.29.115.135-.002.222-.044.282-.113l.002-.002c.067-.074.11-.188.11-.364V16.48a.63.63 0 0 1 1.26 0v3.288h-.007a1.44 1.44 0 0 1-.213.641l-.001.002a1.592 1.592 0 0 1-.592.543l-.002.001a1.74 1.74 0 0 1-.682.189v.006h-.313v-.006a1.788 1.788 0 0 1-.688-.188l-.003-.002a1.592 1.592 0 0 1-.592-.543v-.002a1.44 1.44 0 0 1-.214-.64h-.007v-3.29a.63.63 0 0 1 .63-.629ZM7.286 16.285l.397 1.229.484-1.3a.558.558 0 0 1 1.046.388l-.983 2.656v1.304a.588.588 0 1 1-1.177 0v-1.304l-.953-2.56a.628.628 0 1 1 1.186-.413Z" fill="currentColor"/>
</svg>
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
"token_index": "/token/:hash", "token_index": "/token/:hash",
"token_instance_item": "/token/:hash/instance/:id", "token_instance_item": "/token/:hash/instance/:id",
"address_index": "/address/:id", "address_index": "/address/:id",
"address_contract_verification": "/address/:id/contract_verifications/new", "address_contract_verification": "/address/:id/contract_verification",
"accounts": "/accounts", "accounts": "/accounts",
"apps": "/apps", "apps": "/apps",
"app_index": "/apps/:id", "app_index": "/apps/:id",
......
import type { NextPage } from 'next';
import Head from 'next/head';
import React from 'react';
import type { PageParams } from 'lib/next/address/types';
import getSeo from 'lib/next/address/getSeo';
import ContractVerification from 'ui/pages/ContractVerification';
const ContractVerificationPage: NextPage<PageParams> = ({ id }: PageParams) => {
const { title, description } = getSeo({ id });
return (
<>
<Head>
<title>{ title }</title>
<meta name="description" content={ description }/>
</Head>
<ContractVerification/>
</>
);
};
export default ContractVerificationPage;
export { getServerSideProps } from 'lib/next/getServerSideProps';
...@@ -2,6 +2,7 @@ import { checkboxAnatomy as parts } from '@chakra-ui/anatomy'; ...@@ -2,6 +2,7 @@ import { checkboxAnatomy as parts } from '@chakra-ui/anatomy';
import { import {
createMultiStyleConfigHelpers, createMultiStyleConfigHelpers,
defineStyle, defineStyle,
cssVar,
} from '@chakra-ui/styled-system'; } from '@chakra-ui/styled-system';
import { mode } from '@chakra-ui/theme-tools'; import { mode } from '@chakra-ui/theme-tools';
import { runIfFn } from '@chakra-ui/utils'; import { runIfFn } from '@chakra-ui/utils';
...@@ -9,6 +10,8 @@ import { runIfFn } from '@chakra-ui/utils'; ...@@ -9,6 +10,8 @@ import { runIfFn } from '@chakra-ui/utils';
const { definePartsStyle, defineMultiStyleConfig } = const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(parts.keys); createMultiStyleConfigHelpers(parts.keys);
const $size = cssVar('checkbox-size');
const baseStyleControl = defineStyle((props) => { const baseStyleControl = defineStyle((props) => {
const { colorScheme: c } = props; const { colorScheme: c } = props;
...@@ -28,6 +31,24 @@ const baseStyleControl = defineStyle((props) => { ...@@ -28,6 +31,24 @@ const baseStyleControl = defineStyle((props) => {
}; };
}); });
const sizes = {
sm: definePartsStyle({
control: { [$size.variable]: 'sizes.3' },
label: { fontSize: 'sm' },
icon: { fontSize: '3xs' },
}),
md: definePartsStyle({
control: { [$size.variable]: 'sizes.4' },
label: { fontSize: 'md' },
icon: { fontSize: '2xs' },
}),
lg: definePartsStyle({
control: { [$size.variable]: 'sizes.5' },
label: { fontSize: 'md' },
icon: { fontSize: '2xs' },
}),
};
const baseStyleLabel = defineStyle({ const baseStyleLabel = defineStyle({
_disabled: { opacity: 0.2 }, _disabled: { opacity: 0.2 },
}); });
...@@ -39,6 +60,7 @@ const baseStyle = definePartsStyle((props) => ({ ...@@ -39,6 +60,7 @@ const baseStyle = definePartsStyle((props) => ({
const Checkbox = defineMultiStyleConfig({ const Checkbox = defineMultiStyleConfig({
baseStyle, baseStyle,
sizes,
}); });
export default Checkbox; export default Checkbox;
...@@ -21,8 +21,24 @@ const baseStyle = definePartsStyle({ ...@@ -21,8 +21,24 @@ const baseStyle = definePartsStyle({
container: baseStyleContainer, container: baseStyleContainer,
}); });
const sizes = {
md: definePartsStyle({
control: { w: '4', h: '4' },
label: { fontSize: 'md' },
}),
lg: definePartsStyle({
control: { w: '5', h: '5' },
label: { fontSize: 'md' },
}),
sm: definePartsStyle({
control: { width: '3', height: '3' },
label: { fontSize: 'sm' },
}),
};
const Radio = defineMultiStyleConfig({ const Radio = defineMultiStyleConfig({
baseStyle, baseStyle,
sizes,
}); });
export default Radio; export default Radio;
import { selectAnatomy as parts } from '@chakra-ui/anatomy'; import { selectAnatomy as parts } from '@chakra-ui/anatomy';
import { import {
createMultiStyleConfigHelpers, createMultiStyleConfigHelpers,
defineStyle,
} from '@chakra-ui/styled-system'; } from '@chakra-ui/styled-system';
import { mode } from '@chakra-ui/theme-tools'; import { mode } from '@chakra-ui/theme-tools';
...@@ -26,11 +27,52 @@ const variantOutline = definePartsStyle((props) => { ...@@ -26,11 +27,52 @@ const variantOutline = definePartsStyle((props) => {
}; };
}); });
const iconSpacing = defineStyle({
paddingInlineEnd: '8',
});
const sizes = {
lg: {
...Input.sizes?.lg,
field: {
...Input.sizes?.lg.field,
...iconSpacing,
},
},
md: {
...Input.sizes?.md,
field: {
...Input.sizes?.md.field,
...iconSpacing,
},
},
sm: {
...Input.sizes?.sm,
field: {
...Input.sizes?.sm.field,
...iconSpacing,
},
},
xs: {
...Input.sizes?.xs,
field: {
...Input.sizes?.xs.field,
...iconSpacing,
fontSize: 'sm',
lineHeight: '20px',
},
},
};
const Select = defineMultiStyleConfig({ const Select = defineMultiStyleConfig({
variants: { variants: {
...Input.variants, ...Input.variants,
outline: variantOutline, outline: variantOutline,
}, },
sizes,
defaultProps: {
size: 'xs',
},
}); });
export default Select; export default Select;
...@@ -4,6 +4,10 @@ const semanticTokens = { ...@@ -4,6 +4,10 @@ const semanticTokens = {
'default': 'blackAlpha.200', 'default': 'blackAlpha.200',
_dark: 'whiteAlpha.200', _dark: 'whiteAlpha.200',
}, },
text_secondary: {
'default': 'gray.500',
_dark: 'gray.400',
},
link: { link: {
'default': 'blue.600', 'default': 'blue.600',
_dark: 'blue.300', _dark: 'blue.300',
...@@ -11,6 +15,10 @@ const semanticTokens = { ...@@ -11,6 +15,10 @@ const semanticTokens = {
link_hovered: { link_hovered: {
'default': 'blue.400', 'default': 'blue.400',
}, },
error: {
'default': 'red.400',
_dark: 'red.300',
},
}, },
}; };
......
...@@ -86,7 +86,7 @@ const ContractWriteResultDumb = ({ result, onSettle, txInfo }: Props) => { ...@@ -86,7 +86,7 @@ const ContractWriteResultDumb = ({ result, onSettle, txInfo }: Props) => {
alignItems="center" alignItems="center"
whiteSpace="pre-wrap" whiteSpace="pre-wrap"
wordBreak="break-all" wordBreak="break-all"
color={ txInfo.status === 'error' || isErrorResult ? 'red.600' : undefined } color={ txInfo.status === 'error' || isErrorResult ? 'error' : undefined }
> >
{ content } { content }
</Box> </Box>
......
import { Button, chakra } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import type { SubmitHandler } from 'react-hook-form';
import { useForm, FormProvider } from 'react-hook-form';
import type { FormFields, VerificationMethod } from './types';
import delay from 'lib/delay';
import ContractVerificationFieldMethod, { VERIFICATION_METHODS } from './fields/ContractVerificationFieldMethod';
import ContractVerificationFlattenSourceCode from './methods/ContractVerificationFlattenSourceCode';
import ContractVerificationMultiPartFile from './methods/ContractVerificationMultiPartFile';
import ContractVerificationSourcify from './methods/ContractVerificationSourcify';
import ContractVerificationStandardInput from './methods/ContractVerificationStandardInput';
import ContractVerificationVyperContract from './methods/ContractVerificationVyperContract';
const METHODS = {
flatten_source_code: <ContractVerificationFlattenSourceCode/>,
standard_input: <ContractVerificationStandardInput/>,
sourcify: <ContractVerificationSourcify/>,
multi_part_file: <ContractVerificationMultiPartFile/>,
vyper_contract: <ContractVerificationVyperContract/>,
};
const ContractVerificationForm = () => {
const router = useRouter();
const methodFromQuery = router.query.method?.toString() as VerificationMethod;
const formApi = useForm<FormFields>({
mode: 'onBlur',
defaultValues: {
method: VERIFICATION_METHODS.includes(methodFromQuery) ? methodFromQuery : undefined,
},
});
const { control, handleSubmit, watch, formState } = formApi;
const onFormSubmit: SubmitHandler<FormFields> = React.useCallback(async(data) => {
// eslint-disable-next-line no-console
console.log('__>__', data);
await delay(5_000);
}, []);
const method = watch('method');
const content = METHODS[method] || null;
return (
<FormProvider { ...formApi }>
<chakra.form
noValidate
onSubmit={ handleSubmit(onFormSubmit) }
mt={ 12 }
>
<ContractVerificationFieldMethod control={ control } isDisabled={ Boolean(method) }/>
{ content }
{ Boolean(method) && (
<Button
variant="solid"
size="lg"
type="submit"
mt={ 12 }
isLoading={ formState.isSubmitting }
loadingText="Verify & publish"
>
Verify & publish
</Button>
) }
</chakra.form>
</FormProvider>
);
};
export default React.memo(ContractVerificationForm);
import { chakra, GridItem } from '@chakra-ui/react';
import React from 'react';
import useIsMobile from 'lib/hooks/useIsMobile';
interface Props {
children: [JSX.Element, JSX.Element | null] | (JSX.Element | null);
className?: string;
}
const ContractVerificationFormRow = ({ children, className }: Props) => {
const isMobile = useIsMobile();
const firstChildren = Array.isArray(children) ? children[0] : children;
const secondChildren = Array.isArray(children) ? children[1] : null;
return (
<>
<GridItem className={ className } _notFirst={{ mt: { base: 3, lg: 0 } }}>{ firstChildren }</GridItem>
{ isMobile && !secondChildren ? null : <GridItem fontSize="sm" className={ className } color="text_secondary">{ secondChildren }</GridItem> }
</>
);
};
export default React.memo(chakra(ContractVerificationFormRow));
import { Grid, Text } from '@chakra-ui/react';
import React from 'react';
interface Props {
title: string;
children: React.ReactNode;
}
const ContractVerificationMethod = ({ title, children }: Props) => {
const ref = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
ref.current?.scrollIntoView({ behavior: 'smooth' });
}, []);
return (
<section ref={ ref }>
<Text variant="secondary" mt={ 12 } mb={ 5 } fontSize="sm">{ title }</Text>
<Grid columnGap="30px" rowGap={{ base: 2, lg: 4 }} templateColumns={{ base: '1fr', lg: 'minmax(auto, 680px) minmax(0, 340px)' }}>
{ children }
</Grid>
</section>
);
};
export default React.memo(ContractVerificationMethod);
import { FormControl, Link, Textarea } from '@chakra-ui/react';
import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import { Controller, useFormContext } from 'react-hook-form';
import type { FormFields } from '../types';
import InputPlaceholder from 'ui/shared/InputPlaceholder';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
const ContractVerificationFieldAbiEncodedArgs = () => {
const { formState, control } = useFormContext<FormFields>();
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'abi_encoded_args'>}) => {
return (
<FormControl variant="floating" id={ field.name } size={{ base: 'md', lg: 'lg' }}>
<Textarea
{ ...field }
maxLength={ 255 }
isDisabled={ formState.isSubmitting }
/>
<InputPlaceholder text="ABI-encoded Constructor Arguments"/>
</FormControl>
);
}, [ formState.isSubmitting ]);
return (
<ContractVerificationFormRow>
<Controller
name="abi_encoded_args"
control={ control }
render={ renderControl }
/>
<>
<span>Add arguments in </span>
<Link href="https://solidity.readthedocs.io/en/develop/abi-spec.html" target="_blank">ABI hex encoded form</Link>
<span> if required by the contract. Constructor arguments are written right to left, and will be found at the end of the input created bytecode.</span>
<span> They may also be </span>
<Link href="https://abi.hashex.org/" target="_blank">parsed here</Link>
<span>.</span>
</>
</ContractVerificationFormRow>
);
};
export default React.memo(ContractVerificationFieldAbiEncodedArgs);
import { FormControl, Link, Textarea } from '@chakra-ui/react';
import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import { useFormContext, Controller } from 'react-hook-form';
import type { FormFields } from '../types';
import InputPlaceholder from 'ui/shared/InputPlaceholder';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
interface Props {
isVyper?: boolean;
}
const ContractVerificationFieldCode = ({ isVyper }: Props) => {
const { formState, control } = useFormContext<FormFields>();
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'code'>}) => {
const error = 'code' in formState.errors ? formState.errors.code : undefined;
return (
<FormControl variant="floating" id={ field.name } isRequired size={{ base: 'md', lg: 'lg' }}>
<Textarea
{ ...field }
isInvalid={ Boolean(error) }
isDisabled={ formState.isSubmitting }
required
maxLength={ 255 }
/>
<InputPlaceholder text="Contract code" error={ error }/>
</FormControl>
);
}, [ formState.errors, formState.isSubmitting ]);
return (
<ContractVerificationFormRow>
<Controller
name="code"
control={ control }
render={ renderControl }
rules={{ required: true }}
/>
{ isVyper ? null : (
<>
<span>We recommend using flattened code. This is necessary if your code utilizes a library or inherits dependencies. Use the </span>
<Link href="https://github.com/poanetwork/solidity-flattener" target="_blank">POA solidity flattener</Link>
<span> or the </span>
<Link href="https://www.npmjs.com/package/truffle-flattener" target="_blank">Truffle flattener</Link>
</>
) }
</ContractVerificationFormRow>
);
};
export default React.memo(ContractVerificationFieldCode);
import { Code, Select, Checkbox } from '@chakra-ui/react';
import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import { useFormContext, Controller } from 'react-hook-form';
import type { FormFields } from '../types';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
const COMPILERS = [
'v0.8.17+commit.8df45f5f',
'v0.8.16+commit.07a7930e',
'v0.8.15+commit.e14f2714',
];
const COMPILERS_NIGHTLY = [
'v0.8.18-nightly.2022.11.23+commit.eb2f874e',
'v0.8.17-nightly.2022.8.24+commit.22a0c46e',
'v0.8.16-nightly.2022.7.6+commit.b6f11b33',
];
interface Props {
isVyper?: boolean;
}
const ContractVerificationFieldCompiler = ({ isVyper }: Props) => {
const [ isNightly, setIsNightly ] = React.useState(false);
const { formState, control } = useFormContext<FormFields>();
const handleCheckboxChange = React.useCallback(() => {
setIsNightly(prev => !prev);
}, []);
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'compiler'>}) => {
const error = 'compiler' in formState.errors ? formState.errors.compiler : undefined;
return (
<Select
{ ...field }
size={{ base: 'md', lg: 'lg' }}
placeholder="Compiler"
isInvalid={ Boolean(error) }
isDisabled={ formState.isSubmitting }
>
{ [ ...COMPILERS, ...(isNightly ? COMPILERS_NIGHTLY : []) ].map((option) => <option key={ option } value={ option }>{ option }</option>) }
</Select>
);
}, [ formState.errors, formState.isSubmitting, isNightly ]);
return (
<ContractVerificationFormRow>
<>
<Controller
name="compiler"
control={ control }
render={ renderControl }
rules={{ required: true }}
/>
{ !isVyper && (
<Checkbox
size="lg"
mt={ 3 }
onChange={ handleCheckboxChange }
isDisabled={ formState.isSubmitting }
>
Include nightly builds
</Checkbox>
) }
</>
{ isVyper ? null : (
<>
<span>The compiler version is specified in </span>
<Code color="text_secondary">pragma solidity X.X.X</Code>
<span>. Use the compiler version rather than the nightly build. If using the Solidity compiler, run </span>
<Code color="text_secondary">solc —version</Code>
<span> to check.</span>
</>
) }
</ContractVerificationFormRow>
);
};
export default React.memo(ContractVerificationFieldCompiler);
import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import { Controller, useFormContext } from 'react-hook-form';
import type { FormFields } from '../types';
import CheckboxInput from 'ui/shared/CheckboxInput';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
const ContractVerificationFieldConstArgs = () => {
const { formState, control } = useFormContext<FormFields>();
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'constructor_args'>}) => (
<CheckboxInput<FormFields, 'constructor_args'>
text="Try to fetch constructor arguments automatically"
field={ field }
isDisabled={ formState.isSubmitting }
/>
), [ formState.isSubmitting ]);
return (
<ContractVerificationFormRow>
<Controller
name="constructor_args"
control={ control }
render={ renderControl }
/>
</ContractVerificationFormRow>
);
};
export default React.memo(ContractVerificationFieldConstArgs);
import { Link, Select } from '@chakra-ui/react';
import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import { useFormContext, Controller } from 'react-hook-form';
import type { FormFields } from '../types';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
const VERSIONS = [
'default',
'london',
'berlin',
];
const ContractVerificationFieldEvmVersion = () => {
const { formState, control } = useFormContext<FormFields>();
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'evm_version'>}) => {
const error = 'evm_version' in formState.errors ? formState.errors.evm_version : undefined;
return (
<Select
{ ...field }
size={{ base: 'md', lg: 'lg' }}
placeholder="EVM Version"
isInvalid={ Boolean(error) }
isDisabled={ formState.isSubmitting }
>
{ VERSIONS.map((option) => <option key={ option } value={ option }>{ option }</option>) }
</Select>
);
}, [ formState.errors, formState.isSubmitting ]);
return (
<ContractVerificationFormRow>
<Controller
name="evm_version"
control={ control }
render={ renderControl }
rules={{ required: true }}
/>
<>
<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>
<Link href="https://forum.poa.network/t/smart-contract-verification-evm-version-details/2318" target="_blank">EVM version details</Link>
</>
</ContractVerificationFormRow>
);
};
export default React.memo(ContractVerificationFieldEvmVersion);
import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import { useFormContext, Controller } from 'react-hook-form';
import type { FormFields } from '../types';
import CheckboxInput from 'ui/shared/CheckboxInput';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
const ContractVerificationFieldIsYul = () => {
const { formState, control } = useFormContext<FormFields>();
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'is_yul'>}) => (
<CheckboxInput<FormFields, 'is_yul'> text="Is Yul contract" field={ field } isDisabled={ formState.isSubmitting }/>
), [ formState.isSubmitting ]);
return (
<ContractVerificationFormRow>
<Controller
name="is_yul"
control={ control }
render={ renderControl }
/>
</ContractVerificationFormRow>
);
};
export default React.memo(ContractVerificationFieldIsYul);
import { Checkbox } from '@chakra-ui/react';
import React from 'react';
import { useFieldArray, useFormContext } from 'react-hook-form';
import type { FormFields } from '../types';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
import ContractVerificationFieldLibraryItem from './ContractVerificationFieldLibraryItem';
const ContractVerificationFieldLibraries = () => {
const { formState, control } = useFormContext<FormFields>();
const { fields, append, remove, insert } = useFieldArray({
name: 'libraries',
control,
});
const [ isEnabled, setIsEnabled ] = React.useState(fields.length > 0);
const handleCheckboxChange = React.useCallback(() => {
if (!isEnabled) {
append({ name: '', address: '' });
} else {
remove();
}
setIsEnabled(prev => !prev);
}, [ append, isEnabled, remove ]);
const handleAddFieldClick = React.useCallback((index: number) => {
insert(index + 1, { name: '', address: '' });
}, [ insert ]);
const handleRemoveFieldClick = React.useCallback((index: number) => {
remove(index);
}, [ remove ]);
return (
<>
<ContractVerificationFormRow>
<Checkbox
size="lg"
onChange={ handleCheckboxChange }
mt={ 9 }
>
Add contract libraries
</Checkbox>
</ContractVerificationFormRow>
{ fields.map((field, index) => (
<ContractVerificationFieldLibraryItem
key={ field.id }
index={ index }
control={ control }
fieldsLength={ fields.length }
onAddFieldClick={ handleAddFieldClick }
onRemoveFieldClick={ handleRemoveFieldClick }
error={ 'libraries' in formState.errors ? formState.errors.libraries?.[index] : undefined }
/>
)) }
</>
);
};
export default React.memo(ContractVerificationFieldLibraries);
import { Flex, FormControl, Icon, IconButton, Input, Text } from '@chakra-ui/react';
import React from 'react';
import type { Control, ControllerRenderProps, FieldError } from 'react-hook-form';
import { Controller } from 'react-hook-form';
import type { FormFields } from '../types';
import minusIcon from 'icons/minus.svg';
import plusIcon from 'icons/plus.svg';
import { ADDRESS_REGEXP } from 'lib/validations/address';
import InputPlaceholder from 'ui/shared/InputPlaceholder';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
const LIMIT = 10;
interface Props {
control: Control<FormFields>;
index: number;
fieldsLength: number;
error?: {
name?: FieldError;
address?: FieldError;
};
onAddFieldClick: (index: number) => void;
onRemoveFieldClick: (index: number) => void;
}
const ContractVerificationFieldLibraryItem = ({ control, index, fieldsLength, onAddFieldClick, onRemoveFieldClick, error }: Props) => {
const ref = React.useRef<HTMLDivElement>(null);
const renderNameControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, `libraries.${ number }.name`>}) => {
return (
<FormControl variant="floating" id={ field.name } isRequired size={{ base: 'md', lg: 'lg' }}>
<Input
{ ...field }
required
isInvalid={ Boolean(error?.name) }
maxLength={ 255 }
autoComplete="off"
/>
<InputPlaceholder text="Library name (.sol file)" error={ error?.name }/>
</FormControl>
);
}, [ error?.name ]);
const renderAddressControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, `libraries.${ number }.address`>}) => {
return (
<FormControl variant="floating" id={ field.name } isRequired size={{ base: 'md', lg: 'lg' }}>
<Input
{ ...field }
isInvalid={ Boolean(error?.address) }
required
autoComplete="off"
/>
<InputPlaceholder text="Library address (0x...)" error={ error?.address }/>
</FormControl>
);
}, [ error?.address ]);
const handleAddButtonClick = React.useCallback(() => {
onAddFieldClick(index);
}, [ index, onAddFieldClick ]);
const handleRemoveButtonClick = React.useCallback(() => {
onRemoveFieldClick(index);
}, [ index, onRemoveFieldClick ]);
React.useEffect(() => {
ref.current?.scrollIntoView({ behavior: 'smooth' });
}, []);
return (
<>
<ContractVerificationFormRow>
<Flex alignItems="center" justifyContent="space-between" ref={ ref } mt={ index !== 0 ? 6 : 0 }>
<Text variant="secondary" fontSize="sm">Contract library { index + 1 }</Text>
<Flex columnGap={ 5 }>
{ fieldsLength > 1 && (
<IconButton
aria-label="delete"
variant="outline"
w="30px"
h="30px"
onClick={ handleRemoveButtonClick }
icon={ <Icon as={ minusIcon } w="20px" h="20px"/> }
/>
) }
{ fieldsLength < LIMIT && (
<IconButton
aria-label="add"
variant="outline"
w="30px"
h="30px"
onClick={ handleAddButtonClick }
icon={ <Icon as={ plusIcon } w="20px" h="20px"/> }
/>
) }
</Flex>
</Flex>
</ContractVerificationFormRow>
<ContractVerificationFormRow>
<Controller
name={ `libraries.${ index }.name` }
control={ control }
render={ renderNameControl }
rules={{ required: true }}
/>
{ index === 0 ? (
<>
A library name called in the .sol file. Multiple libraries (up to 10) may be added for each contract.
</>
) : null }
</ContractVerificationFormRow>
<ContractVerificationFormRow>
<Controller
name={ `libraries.${ index }.address` }
control={ control }
render={ renderAddressControl }
rules={{ required: true, pattern: ADDRESS_REGEXP }}
/>
{ index === 0 ? (
<>
The 0x library address. This can be found in the generated json file or Truffle output (if using truffle).
</>
) : null }
</ContractVerificationFormRow>
</>
);
};
export default React.memo(ContractVerificationFieldLibraryItem);
import {
RadioGroup,
Radio,
Stack,
Text,
Link,
Icon,
chakra,
Popover,
PopoverTrigger,
Portal,
PopoverContent,
PopoverArrow,
PopoverBody,
useColorModeValue,
DarkMode,
useBoolean,
} from '@chakra-ui/react';
import React from 'react';
import type { ControllerRenderProps, Control } from 'react-hook-form';
import { Controller } from 'react-hook-form';
import type { FormFields, VerificationMethod } from '../types';
import infoIcon from 'icons/info.svg';
export const VERIFICATION_METHODS: Array<VerificationMethod> = [
'flatten_source_code',
'standard_input',
'sourcify',
'multi_part_file',
'vyper_contract',
];
interface Props {
control: Control<FormFields>;
isDisabled?: boolean;
}
const ContractVerificationFieldMethod = ({ control, isDisabled }: Props) => {
const [ isPopoverOpen, setIsPopoverOpen ] = useBoolean();
const tooltipBg = useColorModeValue('gray.700', 'gray.900');
const renderItem = React.useCallback((method: VerificationMethod) => {
switch (method) {
case 'flatten_source_code':
return 'Via flattened source code';
case 'standard_input':
return (
<>
<span>Via standard </span>
<Link
href={ isDisabled ? undefined : 'https://docs.soliditylang.org/en/latest/using-the-compiler.html#input-description' }
target="_blank"
cursor={ isDisabled ? 'not-allowed' : 'pointer' }
>
Input JSON
</Link>
</>
);
case 'sourcify':
return (
<>
<span>Via sourcify: sources and metadata JSON file</span>
<Popover trigger="hover" isLazy isOpen={ isDisabled ? false : isPopoverOpen } onOpen={ setIsPopoverOpen.on } onClose={ setIsPopoverOpen.off }>
<PopoverTrigger>
<chakra.span cursor={ isDisabled ? 'not-allowed' : 'pointer' } display="inline-block" verticalAlign="middle" h="24px" ml={ 1 }>
<Icon as={ infoIcon } boxSize={ 5 } color="link" _hover={{ color: 'link_hovered' }}/>
</chakra.span>
</PopoverTrigger>
<Portal>
<PopoverContent bgColor={ tooltipBg }>
<PopoverArrow bgColor={ tooltipBg }/>
<PopoverBody color="white">
<DarkMode>
<div>
<span>Verification through </span>
<Link href="https://sourcify.dev/" target="_blank">Sourcify</Link>
</div>
<div>
<span>a) if smart-contract already verified on Sourcify, it will automatically fetch the data from the </span>
<Link href="https://repo.sourcify.dev/" target="_blank">repo</Link>
</div>
<div>
b) otherwise you will be asked to upload source files and JSON metadata file(s).
</div>
</DarkMode>
</PopoverBody>
</PopoverContent>
</Portal>
</Popover>
</>
);
case 'multi_part_file':
return 'Via multi-part files';
case 'vyper_contract':
return 'Vyper contract';
default:
break;
}
}, [ isDisabled, isPopoverOpen, setIsPopoverOpen.off, setIsPopoverOpen.on, tooltipBg ]);
const renderRadioGroup = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'method'>}) => {
return (
<RadioGroup defaultValue="add" colorScheme="blue" isDisabled={ isDisabled } { ...field }>
<Stack spacing={ 4 }>
{ VERIFICATION_METHODS.map((method) => {
return <Radio key={ method } value={ method } size="lg">{ renderItem(method) }</Radio>;
}) }
</Stack>
</RadioGroup>
);
}, [ isDisabled, renderItem ]);
return (
<section>
<Text variant="secondary" fontSize="sm" mb={ 5 }>Smart-contract verification method</Text>
<Controller
name="method"
control={ control }
render={ renderRadioGroup }
/>
</section>
);
};
export default React.memo(ContractVerificationFieldMethod);
import { chakra, Code, FormControl, Input } from '@chakra-ui/react';
import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import { Controller, useFormContext } from 'react-hook-form';
import type { FormFields } from '../types';
import InputPlaceholder from 'ui/shared/InputPlaceholder';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
interface Props {
hint?: string;
}
const ContractVerificationFieldName = ({ hint }: Props) => {
const { formState, control } = useFormContext<FormFields>();
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'name'>}) => {
const error = 'name' in formState.errors ? formState.errors.name : undefined;
return (
<FormControl variant="floating" id={ field.name } isRequired size={{ base: 'md', lg: 'lg' }}>
<Input
{ ...field }
required
isInvalid={ Boolean(error) }
maxLength={ 255 }
isDisabled={ formState.isSubmitting }
autoComplete="off"
/>
<InputPlaceholder text="Contract name" error={ error }/>
</FormControl>
);
}, [ formState.errors, formState.isSubmitting ]);
return (
<ContractVerificationFormRow>
<Controller
name="name"
control={ control }
render={ renderControl }
rules={{ required: true }}
/>
{ hint ? <span>{ hint }</span> : (
<>
<span>Must match the name specified in the code. For example, in </span>
<Code color="text_secondary">{ `contract MyContract {..}` }</Code>
<span>. <chakra.span fontWeight={ 600 }>MyContract</chakra.span> is the contract name.</span>
</>
) }
</ContractVerificationFormRow>
);
};
export default React.memo(ContractVerificationFieldName);
import { FormControl, Input } from '@chakra-ui/react';
import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import { Controller, useFormContext } from 'react-hook-form';
import type { FormFields } from '../types';
import CheckboxInput from 'ui/shared/CheckboxInput';
import InputPlaceholder from 'ui/shared/InputPlaceholder';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
const ContractVerificationFieldOptimization = () => {
const [ isEnabled, setIsEnabled ] = React.useState(false);
const { formState, control } = useFormContext<FormFields>();
const handleCheckboxChange = React.useCallback(() => {
setIsEnabled(prev => !prev);
}, []);
const renderCheckboxControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'is_optimization_enabled'>}) => (
<CheckboxInput<FormFields, 'is_optimization_enabled'>
text="Optimization enabled"
field={ field }
onChange={ handleCheckboxChange }
isDisabled={ formState.isSubmitting }
/>
), [ formState.isSubmitting, handleCheckboxChange ]);
const renderInputControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'optimization_runs'>}) => {
return (
<FormControl variant="floating" id={ field.name } size={{ base: 'md', lg: 'lg' }}>
<Input
{ ...field }
required
maxLength={ 255 }
isDisabled={ formState.isSubmitting }
autoComplete="off"
/>
<InputPlaceholder text="Optimization runs"/>
</FormControl>
);
}, [ formState.isSubmitting ]);
return (
<>
<ContractVerificationFormRow>
<Controller
name="is_optimization_enabled"
control={ control }
render={ renderCheckboxControl }
/>
</ContractVerificationFormRow>
{ isEnabled && (
<ContractVerificationFormRow>
<Controller
name="optimization_runs"
control={ control }
render={ renderInputControl }
rules={{ required: true }}
/>
</ContractVerificationFormRow>
) }
</>
);
};
export default React.memo(ContractVerificationFieldOptimization);
import { Text, Button, Box, chakra } from '@chakra-ui/react';
import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import { Controller, useFormContext } from 'react-hook-form';
import type { FormFields } from '../types';
import FileInput from 'ui/shared/forms/FileInput';
import FileSnippet from 'ui/shared/forms/FileSnippet';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
interface Props {
accept?: string;
multiple?: boolean;
title: string;
className?: string;
hint: string;
}
const ContractVerificationFieldSources = ({ accept, multiple, title, className, hint }: Props) => {
const { setValue, getValues, control, formState } = useFormContext<FormFields>();
const error = 'sources' in formState.errors ? formState.errors.sources : undefined;
const handleFileRemove = React.useCallback((index?: number) => {
if (index === undefined) {
return;
}
const value = getValues('sources').slice();
value.splice(index, 1);
setValue('sources', value);
}, [ getValues, setValue ]);
const renderFiles = React.useCallback((files: Array<File>) => {
return (
<Box display="grid" gridTemplateColumns={{ base: '1fr', lg: '1fr 1fr' }} columnGap={ 3 } rowGap={ 3 }>
{ files.map((file, index) => (
<FileSnippet
key={ file.name + file.lastModified }
file={ file }
maxW="initial"
onRemove={ handleFileRemove }
index={ index }
isDisabled={ formState.isSubmitting }
/>
)) }
</Box>
);
}, [ formState.isSubmitting, handleFileRemove ]);
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'sources'>}) => (
<>
<FileInput<FormFields, 'sources'> accept={ accept } multiple={ multiple } field={ field }>
<Button variant="outline" size="sm" display={ field.value && field.value.length > 0 ? 'none' : 'block' }>
Upload file{ multiple ? 's' : '' }
</Button>
</FileInput>
{ field.value && field.value.length > 0 && renderFiles(field.value) }
{ error && (
<Box fontSize="sm" mt={ 2 } color="error">
{ error.type === 'required' ? 'Field is required' : error.message }
</Box>
) }
</>
), [ accept, error, multiple, renderFiles ]);
return (
<>
<ContractVerificationFormRow >
<Text fontWeight={ 500 } className={ className } mt={ 4 }>{ title }</Text>
</ContractVerificationFormRow>
<ContractVerificationFormRow>
<Controller
name="sources"
control={ control }
render={ renderControl }
rules={{ required: true }}
/>
{ hint ? <span>{ hint }</span> : null }
</ContractVerificationFormRow>
</>
);
};
export default React.memo(chakra(ContractVerificationFieldSources));
import React from 'react';
import ContractVerificationMethod from '../ContractVerificationMethod';
import ContractVerificationFieldCode from '../fields/ContractVerificationFieldCode';
import ContractVerificationFieldCompiler from '../fields/ContractVerificationFieldCompiler';
import ContractVerificationFieldConstArgs from '../fields/ContractVerificationFieldConstArgs';
import ContractVerificationFieldEvmVersion from '../fields/ContractVerificationFieldEvmVersion';
import ContractVerificationFieldIsYul from '../fields/ContractVerificationFieldIsYul';
import ContractVerificationFieldLibraries from '../fields/ContractVerificationFieldLibraries';
import ContractVerificationFieldName from '../fields/ContractVerificationFieldName';
import ContractVerificationFieldOptimization from '../fields/ContractVerificationFieldOptimization';
const ContractVerificationFlattenSourceCode = () => {
return (
<ContractVerificationMethod title="New Solidity/Yul Smart Contract Verification">
<ContractVerificationFieldIsYul/>
<ContractVerificationFieldName/>
<ContractVerificationFieldCompiler/>
<ContractVerificationFieldEvmVersion/>
<ContractVerificationFieldOptimization/>
<ContractVerificationFieldCode/>
<ContractVerificationFieldConstArgs/>
<ContractVerificationFieldLibraries/>
</ContractVerificationMethod>
);
};
export default React.memo(ContractVerificationFlattenSourceCode);
import React from 'react';
import ContractVerificationMethod from '../ContractVerificationMethod';
import ContractVerificationFieldCompiler from '../fields/ContractVerificationFieldCompiler';
import ContractVerificationFieldEvmVersion from '../fields/ContractVerificationFieldEvmVersion';
import ContractVerificationFieldLibraries from '../fields/ContractVerificationFieldLibraries';
import ContractVerificationFieldOptimization from '../fields/ContractVerificationFieldOptimization';
import ContractVerificationFieldSources from '../fields/ContractVerificationFieldSources';
const ContractVerificationMultiPartFile = () => {
return (
<ContractVerificationMethod title="New Solidity/Yul Smart Contract Verification">
<ContractVerificationFieldCompiler/>
<ContractVerificationFieldEvmVersion/>
<ContractVerificationFieldOptimization/>
<ContractVerificationFieldSources
accept=".sol,.yul"
multiple
title="Sources *.sol or *.yul files"
hint="Upload all Solidity or Yul contract source files."
/>
<ContractVerificationFieldLibraries/>
</ContractVerificationMethod>
);
};
export default React.memo(ContractVerificationMultiPartFile);
import React from 'react';
import ContractVerificationMethod from '../ContractVerificationMethod';
import ContractVerificationFieldSources from '../fields/ContractVerificationFieldSources';
const ContractVerificationSourcify = () => {
return (
<ContractVerificationMethod title="New Smart Contract Verification">
<ContractVerificationFieldSources
accept=".json"
multiple
title="Sources and Metadata JSON" mt={ 0 }
hint="Upload all Solidity contract source files and JSON metadata file(s) created during contract compilation."
/>
</ContractVerificationMethod>
);
};
export default React.memo(ContractVerificationSourcify);
import React from 'react';
import ContractVerificationMethod from '../ContractVerificationMethod';
import ContractVerificationFieldCompiler from '../fields/ContractVerificationFieldCompiler';
import ContractVerificationFieldConstArgs from '../fields/ContractVerificationFieldConstArgs';
import ContractVerificationFieldName from '../fields/ContractVerificationFieldName';
import ContractVerificationFieldSources from '../fields/ContractVerificationFieldSources';
const ContractVerificationStandardInput = () => {
return (
<ContractVerificationMethod title="New Smart Contract Verification">
<ContractVerificationFieldName/>
<ContractVerificationFieldCompiler/>
<ContractVerificationFieldSources
accept=".json"
title="Standard Input JSON"
hint="Upload the standard input JSON file created during contract compilation."
/>
<ContractVerificationFieldConstArgs/>
</ContractVerificationMethod>
);
};
export default React.memo(ContractVerificationStandardInput);
import React from 'react';
import ContractVerificationMethod from '../ContractVerificationMethod';
import ContractVerificationFieldAbiEncodedArgs from '../fields/ContractVerificationFieldAbiEncodedArgs';
import ContractVerificationFieldCode from '../fields/ContractVerificationFieldCode';
import ContractVerificationFieldCompiler from '../fields/ContractVerificationFieldCompiler';
import ContractVerificationFieldName from '../fields/ContractVerificationFieldName';
const ContractVerificationVyperContract = () => {
return (
<ContractVerificationMethod title="New Vyper Smart Contract Verification">
<ContractVerificationFieldName hint="Must match the name specified in the code."/>
<ContractVerificationFieldCompiler isVyper/>
<ContractVerificationFieldCode isVyper/>
<ContractVerificationFieldAbiEncodedArgs/>
</ContractVerificationMethod>
);
};
export default React.memo(ContractVerificationVyperContract);
export type VerificationMethod = 'flatten_source_code' | 'standard_input' | 'sourcify' | 'multi_part_file' | 'vyper_contract'
export interface ContractLibrary {
name: string;
address: string;
}
export interface FormFieldsFlattenSourceCode {
method: 'flatten_source_code';
is_yul: boolean;
name: string;
compiler: string;
evm_version: string;
is_optimization_enabled: boolean;
optimization_runs: string;
code: string;
constructor_args: boolean;
libraries: Array<ContractLibrary>;
}
export interface FormFieldsStandardInput {
method: 'standard_input';
name: string;
compiler: string;
sources: Array<File>;
}
export interface FormFieldsSourcify {
method: 'sourcify';
sources: Array<File>;
}
export interface FormFieldsMultiPartFile {
method: 'multi_part_file';
compiler: string;
evm_version: string;
is_optimization_enabled: boolean;
optimization_runs: string;
sources: Array<File>;
}
export interface FormFieldsVyperContract {
method: 'vyper_contract';
name: string;
compiler: string;
code: string;
abi_encoded_args: string;
}
export type FormFields = FormFieldsFlattenSourceCode | FormFieldsStandardInput | FormFieldsSourcify |
FormFieldsMultiPartFile | FormFieldsVyperContract;
import { Text } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import { useAppContext } from 'lib/appContext';
import isBrowser from 'lib/isBrowser';
import link from 'lib/link/link';
import ContractVerificationForm from 'ui/contractVerification/ContractVerificationForm';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
const ContractVerification = () => {
const appProps = useAppContext();
const isInBrowser = isBrowser();
const referrer = isInBrowser ? window.document.referrer : appProps.referrer;
const hasGoBackLink = referrer && referrer.includes('/address');
const router = useRouter();
const hash = router.query.id?.toString();
const method = router.query.id?.toString();
React.useEffect(() => {
if (method && hash) {
router.replace(link('address_contract_verification', { id: hash }), undefined, { scroll: false, shallow: true });
}
// onMount only
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ ]);
return (
<Page>
<PageTitle
text="New smart contract verification"
backLinkUrl={ hasGoBackLink ? referrer : undefined }
backLinkLabel="Back to contract"
/>
{ hash && (
<Address>
<AddressIcon address={{ hash, is_contract: true, implementation_name: null }} flexShrink={ 0 }/>
<Text fontFamily="heading" ml={ 2 } fontWeight={ 500 } fontSize="lg" w={{ base: '100%', lg: 'auto' }} whiteSpace="nowrap" overflow="hidden">
<HashStringShortenDynamic hash={ hash }/>
</Text>
<CopyToClipboard text={ hash }/>
</Address>
) }
<ContractVerificationForm/>
</Page>
);
};
export default ContractVerification;
...@@ -7,20 +7,31 @@ import type { ControllerRenderProps, FieldValues, Path } from 'react-hook-form'; ...@@ -7,20 +7,31 @@ import type { ControllerRenderProps, FieldValues, Path } from 'react-hook-form';
type Props<TInputs extends FieldValues, TInputName extends Path<TInputs>> = { type Props<TInputs extends FieldValues, TInputName extends Path<TInputs>> = {
field: ControllerRenderProps<TInputs, TInputName>; field: ControllerRenderProps<TInputs, TInputName>;
text: string; text: string;
onChange?: () => void;
isDisabled?: boolean;
} }
export default function CheckboxInput<Inputs extends FieldValues, Name extends Path<Inputs>>( export default function CheckboxInput<Inputs extends FieldValues, Name extends Path<Inputs>>(
{ {
field, field,
text, text,
onChange,
isDisabled,
}: Props<Inputs, Name>) { }: Props<Inputs, Name>) {
const handleChange: typeof field.onChange = React.useCallback((...args) => {
field.onChange(...args);
onChange?.();
}, [ field, onChange ]);
return ( return (
<Checkbox <Checkbox
isChecked={ field.value } isChecked={ field.value }
onChange={ field.onChange } onChange={ handleChange }
ref={ field.ref } ref={ field.ref }
colorScheme="blue" colorScheme="blue"
size="lg" size="lg"
isDisabled={ isDisabled }
> >
{ text } { text }
</Checkbox> </Checkbox>
......
...@@ -4,7 +4,7 @@ import type { FieldError } from 'react-hook-form'; ...@@ -4,7 +4,7 @@ import type { FieldError } from 'react-hook-form';
interface Props { interface Props {
text: string; text: string;
error?: FieldError; error?: Partial<FieldError>;
} }
const InputPlaceholder = ({ text, error }: Props) => { const InputPlaceholder = ({ text, error }: Props) => {
......
...@@ -21,7 +21,7 @@ const RawInputData = ({ hex }: Props) => { ...@@ -21,7 +21,7 @@ const RawInputData = ({ hex }: Props) => {
return ( return (
<Box w="100%"> <Box w="100%">
<Flex justifyContent="space-between" alignItems="center"> <Flex justifyContent="space-between" alignItems="center">
<Select size="sm" borderRadius="base" value={ selectedDataType } onChange={ handleSelectChange } focusBorderColor="none" w="auto"> <Select size="xs" borderRadius="base" value={ selectedDataType } onChange={ handleSelectChange } focusBorderColor="none" w="auto">
{ OPTIONS.map((option) => <option key={ option } value={ option }>{ option }</option>) } { OPTIONS.map((option) => <option key={ option } value={ option }>{ option }</option>) }
</Select> </Select>
<CopyToClipboard text={ hex }/> <CopyToClipboard text={ hex }/>
......
import { InputGroup, VisuallyHiddenInput } from '@chakra-ui/react';
import type { ChangeEvent } from 'react';
import React from 'react';
import type { ControllerRenderProps, FieldValues, Path } from 'react-hook-form';
interface Props<V extends FieldValues, N extends Path<V>> {
children: React.ReactNode;
field: ControllerRenderProps<V, N>;
accept?: string;
multiple?: boolean;
}
const FileInput = <Values extends FieldValues, Names extends Path<Values>>({ children, accept, multiple, field }: Props<Values, Names>) => {
const ref = React.useRef<HTMLInputElement>(null);
const handleInputChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => {
const fileList = event.target.files;
if (!fileList) {
return;
}
const files = Array.from(fileList);
field.onChange(files);
field.onBlur();
}, [ field ]);
const handleClick = React.useCallback(() => {
ref.current?.click();
}, []);
const handleInputBlur = React.useCallback(() => {
field.onBlur();
}, [ field ]);
return (
<InputGroup onClick={ handleClick } onBlur={ handleInputBlur }>
<VisuallyHiddenInput
type="file"
onChange={ handleInputChange }
ref={ ref }
accept={ accept }
multiple={ multiple }
/>
{ children }
</InputGroup>
);
};
export default FileInput;
import { Box, Flex, Icon, Text, useColorModeValue, IconButton, chakra } from '@chakra-ui/react';
import React from 'react';
import CrossIcon from 'icons/cross.svg';
import imageIcon from 'icons/files/image.svg';
import jsonIcon from 'icons/files/json.svg';
import solIcon from 'icons/files/sol.svg';
import yulIcon from 'icons/files/yul.svg';
import { shortenNumberWithLetter } from 'lib/formatters';
const FILE_ICONS: Record<string, React.FunctionComponent<React.SVGAttributes<SVGElement>>> = {
'.json': jsonIcon,
'.sol': solIcon,
'.yul': yulIcon,
};
function getFileExtension(fileName: string) {
const chunks = fileName.split('.');
if (chunks.length === 1) {
return '';
}
return '.' + chunks[chunks.length - 1];
}
interface Props {
file: File;
className?: string;
index?: number;
onRemove?: (index?: number) => void;
isDisabled?: boolean;
}
const FileSnippet = ({ file, className, index, onRemove, isDisabled }: Props) => {
const handleRemove = React.useCallback(() => {
onRemove?.(index);
}, [ index, onRemove ]);
const fileExtension = getFileExtension(file.name);
const fileIcon = FILE_ICONS[fileExtension] || imageIcon;
return (
<Flex
p={ 3 }
borderWidth="2px"
borderRadius="md"
borderColor={ useColorModeValue('blackAlpha.100', 'whiteAlpha.200') }
maxW="300px"
overflow="hidden"
className={ className }
>
<Icon as={ fileIcon } boxSize="50px" color={ useColorModeValue('gray.600', 'gray.400') } mr={ 2 }/>
<Box width="calc(100% - 58px - 24px)" >
<Text fontWeight={ 600 } overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap">{ file.name }</Text>
<Text variant="secondary" mt={ 1 }>{ shortenNumberWithLetter(file.size) }B</Text>
</Box>
<IconButton
aria-label="remove"
icon={ <CrossIcon/> }
boxSize={ 6 }
variant="simple"
display="inline-block"
flexShrink={ 0 }
ml="auto"
onClick={ handleRemove }
isDisabled={ isDisabled }
/>
</Flex>
);
};
export default React.memo(chakra(FileSnippet));
...@@ -66,7 +66,7 @@ const LogTopic = ({ hex, index }: Props) => { ...@@ -66,7 +66,7 @@ const LogTopic = ({ hex, index }: Props) => {
</Button> </Button>
{ index !== 0 && ( { index !== 0 && (
<Select <Select
size="sm" size="xs"
borderRadius="base" borderRadius="base"
value={ selectedDataType } value={ selectedDataType }
onChange={ handleSelectChange } onChange={ handleSelectChange }
......
...@@ -21,7 +21,7 @@ const NetworkMenuContentMobile = () => { ...@@ -21,7 +21,7 @@ const NetworkMenuContentMobile = () => {
return ( return (
<Box mt={ 6 }> <Box mt={ 6 }>
<Select size="sm" borderRadius="base" value={ selectedTab } onChange={ handleSelectChange } focusBorderColor="none"> <Select size="xs" borderRadius="base" value={ selectedTab } onChange={ handleSelectChange } focusBorderColor="none">
{ TABS.map((tab) => <option key={ tab } value={ tab }>{ capitalize(tab) }</option>) } { TABS.map((tab) => <option key={ tab } value={ tab }>{ capitalize(tab) }</option>) }
</Select> </Select>
<VStack as="ul" spacing={ 2 } alignItems="stretch" mt={ 6 }> <VStack as="ul" spacing={ 2 } alignItems="stretch" mt={ 6 }>
......
...@@ -99,7 +99,7 @@ const TxDetails = () => { ...@@ -99,7 +99,7 @@ const TxDetails = () => {
const executionFailedBadge = toAddress.is_contract && Boolean(data.status) && data.result !== 'success' ? ( const executionFailedBadge = toAddress.is_contract && Boolean(data.status) && data.result !== 'success' ? (
<Tooltip label="Error occurred during contract execution"> <Tooltip label="Error occurred during contract execution">
<chakra.span display="inline-flex" ml={ 2 } mr={ 1 }> <chakra.span display="inline-flex" ml={ 2 } mr={ 1 }>
<Icon as={ errorIcon } boxSize={ 4 } color="red.500" cursor="pointer"/> <Icon as={ errorIcon } boxSize={ 4 } color="error" cursor="pointer"/>
</chakra.span> </chakra.span>
</Tooltip> </Tooltip>
) : null; ) : null;
......
...@@ -45,7 +45,7 @@ const TxStateStorageItem = ({ storageItem }: {storageItem: TTxStateItemStorage}) ...@@ -45,7 +45,7 @@ const TxStateStorageItem = ({ storageItem }: {storageItem: TTxStateItemStorage})
<GridItem display="flex" flexDir="row" columnGap={ 3 } alignItems="center" > <GridItem display="flex" flexDir="row" columnGap={ 3 } alignItems="center" >
{ item.select && ( { item.select && (
<Select <Select
size="sm" size="xs"
borderRadius="base" borderRadius="base"
focusBorderColor="none" focusBorderColor="none"
display="inline-block" display="inline-block"
......
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