Commit 459e5ea0 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Contract: License (#1668)

* display license on contract page and in the list

* add new field to the form

* review fixes
parent 20d8c0cd
import type { ContractLicense } from 'types/client/contract';
export const CONTRACT_LICENSES: Array<ContractLicense> = [
{
type: 'none',
label: 'None',
title: 'No License',
url: 'https://choosealicense.com/no-permission/',
},
{
type: 'unlicense',
label: 'Unlicense',
title: 'The Unlicense',
url: 'https://choosealicense.com/licenses/unlicense/',
},
{
type: 'mit',
label: 'MIT',
title: 'MIT License',
url: 'https://choosealicense.com/licenses/mit/',
},
{
type: 'gnu_gpl_v2',
label: 'GNU GPLv2',
title: 'GNU General Public License v2.0',
url: 'https://choosealicense.com/licenses/gpl-2.0/',
},
{
type: 'gnu_gpl_v3',
label: 'GNU GPLv3',
title: 'GNU General Public License v3.0',
url: 'https://choosealicense.com/licenses/gpl-3.0/',
},
{
type: 'gnu_lgpl_v2_1',
label: 'GNU LGPLv2.1',
title: 'GNU Lesser General Public License v2.1',
url: 'https://choosealicense.com/licenses/lgpl-2.1/',
},
{
type: 'gnu_lgpl_v3',
label: 'GNU LGPLv3',
title: 'GNU Lesser General Public License v3.0',
url: 'https://choosealicense.com/licenses/lgpl-3.0/',
},
{
type: 'bsd_2_clause',
label: 'BSD-2-Clause',
title: 'BSD 2-clause "Simplified" license',
url: 'https://choosealicense.com/licenses/bsd-2-clause/',
},
{
type: 'bsd_3_clause',
label: 'BSD-3-Clause',
title: 'BSD 3-clause "New" Or "Revised" license',
url: 'https://choosealicense.com/licenses/bsd-3-clause/',
},
{
type: 'mpl_2_0',
label: 'MPL-2.0',
title: 'Mozilla Public License 2.0',
url: 'https://choosealicense.com/licenses/mpl-2.0/',
},
{
type: 'osl_3_0',
label: 'OSL-3.0',
title: 'Open Software License 3.0',
url: 'https://choosealicense.com/licenses/osl-3.0/',
},
{
type: 'apache_2_0',
label: 'Apache',
title: 'Apache 2.0',
url: 'https://choosealicense.com/licenses/apache-2.0/',
},
{
type: 'gnu_agpl_v3',
label: 'GNU AGPLv3',
title: 'GNU Affero General Public License',
url: 'https://choosealicense.com/licenses/agpl-3.0/',
},
{
type: 'bsl_1_1',
label: 'BSL 1.1',
title: 'Business Source License',
url: 'https://mariadb.com/bsl11/',
},
];
......@@ -31,6 +31,7 @@ export const verified: Partial<SmartContract> = {
{ address_hash: '0xa62744BeE8646e237441CDbfdedD3458861748A8', name: 'math' },
],
language: 'solidity',
license_type: 'gnu_gpl_v3',
};
export const withMultiplePaths: Partial<SmartContract> = {
......
......@@ -20,6 +20,7 @@ export const contract1: VerifiedContract = {
optimization_enabled: false,
tx_count: 7334224,
verified_at: '2022-09-16T18:49:29.605179Z',
license_type: 'mit',
};
export const contract2: VerifiedContract = {
......@@ -42,6 +43,7 @@ export const contract2: VerifiedContract = {
optimization_enabled: true,
tx_count: 440,
verified_at: '2021-09-07T20:01:56.076979Z',
license_type: 'bsd_3_clause',
};
export const baseResponse: VerifiedContractsResponse = {
......
......@@ -40,6 +40,7 @@ export const CONTRACT_CODE_VERIFIED = {
optimization_runs: 200,
source_code: 'source_code',
verified_at: '2023-02-21T14:39:16.906760Z',
license_type: 'mit',
} as unknown as SmartContract;
export const VERIFIED_CONTRACT_INFO: VerifiedContract = {
......@@ -52,6 +53,7 @@ export const VERIFIED_CONTRACT_INFO: VerifiedContract = {
optimization_enabled: false,
tx_count: 565058,
verified_at: '2023-04-10T13:16:33.884921Z',
license_type: 'mit',
};
export const VERIFIED_CONTRACTS_COUNTERS: VerifiedContractsCounters = {
......
......@@ -3,6 +3,22 @@ import type { Abi, AbiType } from 'abitype';
export type SmartContractMethodArgType = AbiType;
export type SmartContractMethodStateMutability = 'view' | 'nonpayable' | 'payable';
export type SmartContractLicenseType =
'none' |
'unlicense' |
'mit' |
'gnu_gpl_v2' |
'gnu_gpl_v3' |
'gnu_lgpl_v2_1' |
'gnu_lgpl_v3' |
'bsd_2_clause' |
'bsd_3_clause' |
'mpl_2_0' |
'osl_3_0' |
'apache_2_0' |
'gnu_agpl_v3' |
'bsl_1_1';
export interface SmartContract {
deployed_bytecode: string | null;
creation_bytecode: string | null;
......@@ -37,6 +53,7 @@ export interface SmartContract {
verified_twin_address_hash: string | null;
minimal_proxy_address_hash: string | null;
language: string | null;
license_type: SmartContractLicenseType | null;
}
export type SmartContractDecodedConstructorArg = [
......@@ -136,6 +153,7 @@ export interface SmartContractVerificationConfigRaw {
vyper_compiler_versions: Array<string>;
vyper_evm_versions: Array<string>;
is_rust_verifier_microservice_enabled: boolean;
license_types: Record<SmartContractLicenseType, number>;
}
export interface SmartContractVerificationConfig extends SmartContractVerificationConfigRaw {
......
import type { AddressParam } from './addressParams';
import type { SmartContractLicenseType } from './contract';
export interface VerifiedContract {
address: AddressParam;
......@@ -10,6 +11,7 @@ export interface VerifiedContract {
tx_count: number | null;
verified_at: string;
market_cap: string | null;
license_type: SmartContractLicenseType | null;
}
export interface VerifiedContractsResponse {
......
import type { SmartContractLicenseType } from 'types/api/contract';
export interface ContractCodeIde {
title: string;
url: string;
icon_url: string;
}
export interface ContractLicense {
type: SmartContractLicenseType;
url: string;
label: string;
title: string;
}
......@@ -9,6 +9,7 @@ import { route } from 'nextjs-routes';
import config from 'configs/app';
import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
import { CONTRACT_LICENSES } from 'lib/contracts/licenses';
import dayjs from 'lib/date/dayjs';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
......@@ -118,6 +119,23 @@ const ContractCode = ({ addressHash, noSocket }: Props) => {
</Button>
);
const licenseLink = (() => {
if (!data?.license_type) {
return null;
}
const license = CONTRACT_LICENSES.find((license) => license.type === data.license_type);
if (!license || license.type === 'none') {
return null;
}
return (
<LinkExternal href={ license.url }>
{ license.label }
</LinkExternal>
);
})();
const constructorArgs = (() => {
if (!data?.decoded_constructor_args) {
return data?.constructor_args;
......@@ -233,6 +251,7 @@ const ContractCode = ({ addressHash, noSocket }: Props) => {
{ data.name && <InfoItem label="Contract name" content={ data.name } isLoading={ isPlaceholderData }/> }
{ data.compiler_version && <InfoItem label="Compiler version" content={ data.compiler_version } isLoading={ isPlaceholderData }/> }
{ data.evm_version && <InfoItem label="EVM version" content={ data.evm_version } textTransform="capitalize" isLoading={ isPlaceholderData }/> }
{ licenseLink && <InfoItem label="License" content={ licenseLink } isLoading={ isPlaceholderData }/> }
{ typeof data.optimization_enabled === 'boolean' &&
<InfoItem label="Optimization enabled" content={ data.optimization_enabled ? 'true' : 'false' } isLoading={ isPlaceholderData }/> }
{ data.optimization_runs && <InfoItem label="Optimization runs" content={ String(data.optimization_runs) } isLoading={ isPlaceholderData }/> }
......
......@@ -54,6 +54,22 @@ const formConfig: SmartContractVerificationConfig = {
'petersburg',
'istanbul',
],
license_types: {
apache_2_0: 12,
bsd_2_clause: 8,
bsd_3_clause: 9,
bsl_1_1: 14,
gnu_agpl_v3: 13,
gnu_gpl_v2: 4,
gnu_gpl_v3: 5,
gnu_lgpl_v2_1: 6,
gnu_lgpl_v3: 7,
mit: 3,
mpl_2_0: 10,
none: 1,
osl_3_0: 11,
unlicense: 2,
},
};
test('flatten source code method +@dark-mode +@mobile', async({ mount, page }) => {
......@@ -64,9 +80,16 @@ test('flatten source code method +@dark-mode +@mobile', async({ mount, page }) =
{ hooksConfig },
);
// select license
await component.getByLabel(/contract license/i).focus();
await component.getByLabel(/contract license/i).fill('mit');
await page.getByRole('button', { name: /mit license/i }).click();
// select method
await component.getByLabel(/verification method/i).focus();
await component.getByLabel(/verification method/i).type('solidity');
await component.getByLabel(/verification method/i).fill('solidity');
await page.getByRole('button', { name: /flattened source code/i }).click();
await page.getByText(/add contract libraries/i).click();
await page.locator('button[aria-label="add"]').click();
......@@ -81,8 +104,9 @@ test('standard input json method', async({ mount, page }) => {
{ hooksConfig },
);
// select method
await component.getByLabel(/verification method/i).focus();
await component.getByLabel(/verification method/i).type('solidity');
await component.getByLabel(/verification method/i).fill('solidity');
await page.getByRole('button', { name: /standard json input/i }).click();
await expect(component).toHaveScreenshot();
......@@ -102,8 +126,9 @@ test.describe('sourcify', () => {
{ hooksConfig },
);
// select method
await component.getByLabel(/verification method/i).focus();
await component.getByLabel(/verification method/i).type('solidity');
await component.getByLabel(/verification method/i).fill('solidity');
await page.getByRole('button', { name: /sourcify/i }).click();
await page.getByText(/drop files/i).click();
......@@ -129,7 +154,7 @@ test.describe('sourcify', () => {
});
await component.getByLabel(/contract name/i).focus();
await component.getByLabel(/contract name/i).type('e');
await component.getByLabel(/contract name/i).fill('e');
const contractNameOption = page.getByRole('button', { name: /MockERC20/i });
await expect(contractNameOption).toBeVisible();
......@@ -146,8 +171,9 @@ test('multi-part files method', async({ mount, page }) => {
{ hooksConfig },
);
// select method
await component.getByLabel(/verification method/i).focus();
await component.getByLabel(/verification method/i).type('solidity');
await component.getByLabel(/verification method/i).fill('solidity');
await page.getByRole('button', { name: /multi-part files/i }).click();
await expect(component).toHaveScreenshot();
......@@ -161,8 +187,9 @@ test('vyper contract method', async({ mount, page }) => {
{ hooksConfig },
);
// select method
await component.getByLabel(/verification method/i).focus();
await component.getByLabel(/verification method/i).type('vyper');
await component.getByLabel(/verification method/i).fill('vyper');
await page.getByRole('button', { name: /contract/i }).click();
await expect(component).toHaveScreenshot();
......@@ -176,8 +203,9 @@ test('vyper multi-part method', async({ mount, page }) => {
{ hooksConfig },
);
// select method
await component.getByLabel(/verification method/i).focus();
await component.getByLabel(/verification method/i).type('vyper');
await component.getByLabel(/verification method/i).fill('vyper');
await page.getByRole('button', { name: /multi-part files/i }).click();
await expect(component).toHaveScreenshot();
......@@ -191,8 +219,9 @@ test('vyper vyper-standard-input method', async({ mount, page }) => {
{ hooksConfig },
);
// select method
await component.getByLabel(/verification method/i).focus();
await component.getByLabel(/verification method/i).type('vyper');
await component.getByLabel(/verification method/i).fill('vyper');
await page.getByRole('button', { name: /standard json input/i }).click();
await expect(component).toHaveScreenshot();
......
......@@ -18,6 +18,7 @@ import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import ContractVerificationFieldAddress from './fields/ContractVerificationFieldAddress';
import ContractVerificationFieldLicenseType from './fields/ContractVerificationFieldLicenseType';
import ContractVerificationFieldMethod from './fields/ContractVerificationFieldMethod';
import ContractVerificationFlattenSourceCode from './methods/ContractVerificationFlattenSourceCode';
import ContractVerificationMultiPartFile from './methods/ContractVerificationMultiPartFile';
......@@ -37,7 +38,7 @@ interface Props {
const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Props) => {
const formApi = useForm<FormFields>({
mode: 'onBlur',
defaultValues: methodFromQuery ? getDefaultValues(methodFromQuery, config, hash) : undefined,
defaultValues: methodFromQuery ? getDefaultValues(methodFromQuery, config, hash, null) : undefined,
});
const { control, handleSubmit, watch, formState, setError, reset, getFieldState } = formApi;
const submitPromiseResolver = React.useRef<(value: unknown) => void>();
......@@ -161,12 +162,13 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro
};
}, [ config ]);
const method = watch('method');
const licenseType = watch('license_type');
const content = methods[method?.value] || null;
const methodValue = method?.value;
useUpdateEffect(() => {
if (methodValue) {
reset(getDefaultValues(methodValue, config, address || hash));
reset(getDefaultValues(methodValue, config, hash || address, licenseType));
const methodName = METHOD_LABELS[methodValue];
mixpanel.logEvent(mixpanel.EventTypes.CONTRACT_VERIFICATION, { Status: 'Method selected', Method: methodName });
......@@ -183,6 +185,7 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro
>
<Grid as="section" columnGap="30px" rowGap={{ base: 2, lg: 5 }} templateColumns={{ base: '1fr', lg: 'minmax(auto, 680px) minmax(0, 340px)' }}>
{ !hash && <ContractVerificationFieldAddress/> }
<ContractVerificationFieldLicenseType/>
<ContractVerificationFieldMethod
control={ control }
methods={ config.verification_options }
......
......@@ -42,7 +42,7 @@ const ContractVerificationFieldAddress = ({ isReadOnly }: Props) => {
Contract address to verify
</chakra.span>
</ContractVerificationFormRow>
<ContractVerificationFormRow mb={ 3 }>
<ContractVerificationFormRow>
<Controller
name="address"
control={ control }
......
import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import { useFormContext, Controller } from 'react-hook-form';
import type { FormFields } from '../types';
import { CONTRACT_LICENSES } from 'lib/contracts/licenses';
import useIsMobile from 'lib/hooks/useIsMobile';
import FancySelect from 'ui/shared/FancySelect/FancySelect';
const options = CONTRACT_LICENSES.map(({ label, title, type }) => ({ label: `${ title } (${ label })`, value: type }));
import ContractVerificationFormRow from '../ContractVerificationFormRow';
const ContractVerificationFieldLicenseType = () => {
const { formState, control } = useFormContext<FormFields>();
const isMobile = useIsMobile();
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'license_type'>}) => {
const error = 'license_type' in formState.errors ? formState.errors.license_type : undefined;
return (
<FancySelect
{ ...field }
options={ options }
size={ isMobile ? 'md' : 'lg' }
placeholder="Contract license"
isDisabled={ formState.isSubmitting }
error={ error }
/>
);
}, [ formState.errors, formState.isSubmitting, isMobile ]);
return (
<ContractVerificationFormRow>
<Controller
name="license_type"
control={ control }
render={ renderControl }
/>
<span>
For best practices, all contract source code holders, publishers and authors are encouraged to also
specify the accompanying license for their verified contract source code provided.
</span>
</ContractVerificationFormRow>
);
};
export default React.memo(ContractVerificationFieldLicenseType);
......@@ -11,6 +11,7 @@ import {
DarkMode,
ListItem,
OrderedList,
Box,
} from '@chakra-ui/react';
import React from 'react';
import type { ControllerRenderProps, Control } from 'react-hook-form';
......@@ -97,7 +98,7 @@ const ContractVerificationFieldMethod = ({ control, isDisabled, methods }: Props
return (
<>
<div>
<Box mt={{ base: 10, lg: 6 }} gridColumn={{ lg: '1 / 3' }}>
<chakra.span fontWeight={ 500 } fontSize="lg" fontFamily="heading">
Currently, Blockscout supports { methods.length } contract verification methods
</chakra.span>
......@@ -121,8 +122,7 @@ const ContractVerificationFieldMethod = ({ control, isDisabled, methods }: Props
</PopoverContent>
</Portal>
</Popover>
</div>
<div/>
</Box>
<Controller
name="method"
control={ control }
......
import type { SmartContractVerificationMethod } from 'types/api/contract';
import type { SmartContractLicenseType, SmartContractVerificationMethod } from 'types/api/contract';
import type { Option } from 'ui/shared/FancySelect/types';
export interface ContractLibrary {
......@@ -11,6 +11,11 @@ interface MethodOption {
value: SmartContractVerificationMethod;
}
export interface LicenseOption {
label: string;
value: SmartContractLicenseType;
}
export interface FormFieldsFlattenSourceCode {
address: string;
method: MethodOption;
......@@ -24,6 +29,7 @@ export interface FormFieldsFlattenSourceCode {
autodetect_constructor_args: boolean;
constructor_args: string;
libraries: Array<ContractLibrary>;
license_type: LicenseOption | null;
}
export interface FormFieldsStandardInput {
......@@ -34,6 +40,7 @@ export interface FormFieldsStandardInput {
sources: Array<File>;
autodetect_constructor_args: boolean;
constructor_args: string;
license_type: LicenseOption | null;
}
export interface FormFieldsSourcify {
......@@ -41,6 +48,7 @@ export interface FormFieldsSourcify {
method: MethodOption;
sources: Array<File>;
contract_index?: Option;
license_type: LicenseOption | null;
}
export interface FormFieldsMultiPartFile {
......@@ -52,6 +60,7 @@ export interface FormFieldsMultiPartFile {
optimization_runs: string;
sources: Array<File>;
libraries: Array<ContractLibrary>;
license_type: LicenseOption | null;
}
export interface FormFieldsVyperContract {
......@@ -62,6 +71,7 @@ export interface FormFieldsVyperContract {
compiler: Option | null;
code: string;
constructor_args: string | undefined;
license_type: LicenseOption | null;
}
export interface FormFieldsVyperMultiPartFile {
......@@ -71,6 +81,7 @@ export interface FormFieldsVyperMultiPartFile {
evm_version: Option | null;
sources: Array<File>;
interfaces: Array<File>;
license_type: LicenseOption | null;
}
export interface FormFieldsVyperStandardInput {
......@@ -78,6 +89,7 @@ export interface FormFieldsVyperStandardInput {
method: MethodOption;
compiler: Option | null;
sources: Array<File>;
license_type: LicenseOption | null;
}
export type FormFields = FormFieldsFlattenSourceCode | FormFieldsStandardInput | FormFieldsSourcify |
......
......@@ -11,7 +11,12 @@ import type {
FormFieldsVyperMultiPartFile,
FormFieldsVyperStandardInput,
} from './types';
import type { SmartContractVerificationMethod, SmartContractVerificationError, SmartContractVerificationConfig } from 'types/api/contract';
import type {
SmartContractVerificationMethod,
SmartContractVerificationError,
SmartContractVerificationConfig,
SmartContractLicenseType,
} from 'types/api/contract';
import type { Params as FetchParams } from 'lib/hooks/useFetch';
......@@ -52,6 +57,7 @@ export const DEFAULT_VALUES: Record<SmartContractVerificationMethod, FormFields>
autodetect_constructor_args: true,
constructor_args: '',
libraries: [],
license_type: null,
},
'standard-input': {
address: '',
......@@ -64,6 +70,7 @@ export const DEFAULT_VALUES: Record<SmartContractVerificationMethod, FormFields>
sources: [],
autodetect_constructor_args: true,
constructor_args: '',
license_type: null,
},
sourcify: {
address: '',
......@@ -72,6 +79,7 @@ export const DEFAULT_VALUES: Record<SmartContractVerificationMethod, FormFields>
label: METHOD_LABELS.sourcify,
},
sources: [],
license_type: null,
},
'multi-part': {
address: '',
......@@ -85,6 +93,7 @@ export const DEFAULT_VALUES: Record<SmartContractVerificationMethod, FormFields>
optimization_runs: '200',
sources: [],
libraries: [],
license_type: null,
},
'vyper-code': {
address: '',
......@@ -97,6 +106,7 @@ export const DEFAULT_VALUES: Record<SmartContractVerificationMethod, FormFields>
evm_version: null,
code: '',
constructor_args: '',
license_type: null,
},
'vyper-multi-part': {
address: '',
......@@ -107,6 +117,7 @@ export const DEFAULT_VALUES: Record<SmartContractVerificationMethod, FormFields>
compiler: null,
evm_version: null,
sources: [],
license_type: null,
},
'vyper-standard-input': {
address: '',
......@@ -116,11 +127,17 @@ export const DEFAULT_VALUES: Record<SmartContractVerificationMethod, FormFields>
},
compiler: null,
sources: [],
license_type: null,
},
};
export function getDefaultValues(method: SmartContractVerificationMethod, config: SmartContractVerificationConfig, hash?: string) {
const defaultValues = { ...DEFAULT_VALUES[method], address: hash };
export function getDefaultValues(
method: SmartContractVerificationMethod,
config: SmartContractVerificationConfig,
hash: string | undefined,
licenseType: FormFields['license_type'],
) {
const defaultValues: FormFields = { ...DEFAULT_VALUES[method], address: hash || '', license_type: licenseType };
if ('evm_version' in defaultValues) {
if (method === 'flattened-code' || method === 'multi-part') {
......@@ -162,6 +179,8 @@ export function sortVerificationMethods(methodA: SmartContractVerificationMethod
}
export function prepareRequestBody(data: FormFields): FetchParams['body'] {
const defaultLicenseType: SmartContractLicenseType = 'none';
switch (data.method.value) {
case 'flattened-code': {
const _data = data as FormFieldsFlattenSourceCode;
......@@ -176,6 +195,7 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
evm_version: _data.evm_version?.value,
autodetect_constructor_args: _data.autodetect_constructor_args,
constructor_args: _data.constructor_args,
license_type: _data.license_type?.value ?? defaultLicenseType,
};
}
......@@ -184,6 +204,7 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
const body = new FormData();
_data.compiler && body.set('compiler_version', _data.compiler.value);
body.set('license_type', _data.license_type?.value ?? defaultLicenseType);
body.set('contract_name', _data.name);
body.set('autodetect_constructor_args', String(Boolean(_data.autodetect_constructor_args)));
body.set('constructor_args', _data.constructor_args);
......@@ -196,7 +217,8 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
const _data = data as FormFieldsSourcify;
const body = new FormData();
addFilesToFormData(body, _data.sources, 'files');
_data.contract_index && body.set('chosen_contract_index', _data.contract_index.value);
body.set('chosen_contract_index', _data.contract_index?.value ?? defaultLicenseType);
_data.license_type && body.set('license_type', _data.license_type.value);
return body;
}
......@@ -207,6 +229,7 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
const body = new FormData();
_data.compiler && body.set('compiler_version', _data.compiler.value);
_data.evm_version && body.set('evm_version', _data.evm_version.value);
body.set('license_type', _data.license_type?.value ?? defaultLicenseType);
body.set('is_optimization_enabled', String(Boolean(_data.is_optimization_enabled)));
_data.is_optimization_enabled && body.set('optimization_runs', _data.optimization_runs);
......@@ -226,6 +249,7 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
source_code: _data.code,
contract_name: _data.name,
constructor_args: _data.constructor_args,
license_type: _data.license_type?.value ?? defaultLicenseType,
};
}
......@@ -235,6 +259,7 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
const body = new FormData();
_data.compiler && body.set('compiler_version', _data.compiler.value);
_data.evm_version && body.set('evm_version', _data.evm_version.value);
body.set('license_type', _data.license_type?.value ?? defaultLicenseType);
addFilesToFormData(body, _data.sources, 'files');
addFilesToFormData(body, _data.interfaces, 'interfaces');
......@@ -246,6 +271,7 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
const body = new FormData();
_data.compiler && body.set('compiler_version', _data.compiler.value);
body.set('license_type', _data.license_type?.value ?? defaultLicenseType);
addFilesToFormData(body, _data.sources, 'files');
return body;
......
......@@ -5,6 +5,7 @@ import React from 'react';
import type { VerifiedContract } from 'types/api/contracts';
import config from 'configs/app';
import { CONTRACT_LICENSES } from 'lib/contracts/licenses';
import dayjs from 'lib/date/dayjs';
import { currencyUnits } from 'lib/units';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
......@@ -23,6 +24,15 @@ const VerifiedContractsListItem = ({ data, isLoading }: Props) => {
BigNumber(data.coin_balance).div(10 ** config.chain.currency.decimals).dp(6).toFormat() :
'0';
const license = (() => {
const license = CONTRACT_LICENSES.find((license) => license.type === data.license_type);
if (!license || license.type === 'none') {
return '-';
}
return license.label;
})();
return (
<ListItemMobile rowGap={ 3 }>
<Flex w="100%">
......@@ -77,12 +87,12 @@ const VerifiedContractsListItem = ({ data, isLoading }: Props) => {
</Skeleton>
</Flex>
</Flex>
{ /* <Flex columnGap={ 3 }>
<Box fontWeight={ 500 }>Market cap</Box>
<Box color="text_secondary">
N/A
</Box>
</Flex> */ }
<Flex columnGap={ 3 }>
<Skeleton isLoaded={ !isLoading } fontWeight={ 500 }>License</Skeleton>
<Skeleton isLoaded={ !isLoading } color="text_secondary">
<span>{ license }</span>
</Skeleton>
</Flex>
</ListItemMobile>
);
};
......
......@@ -47,7 +47,7 @@ const VerifiedContractsTable = ({ data, sort, setSorting, isLoading }: Props) =>
<Th width="50%">Compiler / version</Th>
<Th width="80px">Settings</Th>
<Th width="150px">Verified</Th>
{ /* <Th width="120px">Market cap</Th> */ }
<Th width="130px">License</Th>
</Tr>
</Thead>
<Tbody>
......
......@@ -5,6 +5,7 @@ import React from 'react';
import type { VerifiedContract } from 'types/api/contracts';
import config from 'configs/app';
import { CONTRACT_LICENSES } from 'lib/contracts/licenses';
import dayjs from 'lib/date/dayjs';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
......@@ -21,6 +22,15 @@ const VerifiedContractsTableItem = ({ data, isLoading }: Props) => {
BigNumber(data.coin_balance).div(10 ** config.chain.currency.decimals).dp(6).toFormat() :
'0';
const license = (() => {
const license = CONTRACT_LICENSES.find((license) => license.type === data.license_type);
if (!license || license.type === 'none') {
return '-';
}
return license.label;
})();
return (
<Tr>
<Td>
......@@ -51,8 +61,10 @@ const VerifiedContractsTableItem = ({ data, isLoading }: Props) => {
<Td>
<Flex flexWrap="wrap" columnGap={ 2 }>
<Skeleton isLoaded={ !isLoading } textTransform="capitalize" my={ 1 }>{ data.language }</Skeleton>
<Skeleton isLoaded={ !isLoading } color="text_secondary" wordBreak="break-all" my={ 1 }>
<span>{ data.compiler_version }</span>
<Skeleton isLoaded={ !isLoading } color="text_secondary" wordBreak="break-all" my={ 1 } cursor="pointer">
<Tooltip label={ data.compiler_version }>
<span>{ data.compiler_version.split('+')[0] }</span>
</Tooltip>
</Skeleton>
</Flex>
</Td>
......@@ -80,9 +92,11 @@ const VerifiedContractsTableItem = ({ data, isLoading }: Props) => {
</Skeleton>
</Flex>
</Td>
{ /* <Td>
N/A
</Td> */ }
<Td>
<Skeleton isLoaded={ !isLoading } my={ 1 } display="inline-block">
{ license }
</Skeleton>
</Td>
</Tr>
);
};
......
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