Commit cb3408dc authored by Max Alekseenko's avatar Max Alekseenko

add security report type

parent 2f05b159
import type { SolidityscanReport } from 'types/api/contract';
export type MarketplaceAppPreview = { export type MarketplaceAppPreview = {
id: string; id: string;
external?: boolean; external?: boolean;
...@@ -25,7 +27,7 @@ export type MarketplaceAppOverview = MarketplaceAppPreview & MarketplaceAppSocia ...@@ -25,7 +27,7 @@ export type MarketplaceAppOverview = MarketplaceAppPreview & MarketplaceAppSocia
} }
export type MarketplaceAppWithSecurityReport = MarketplaceAppOverview & { export type MarketplaceAppWithSecurityReport = MarketplaceAppOverview & {
securityReport: any; // eslint-disable-line @typescript-eslint/no-explicit-any securityReport?: MarketplaceAppSecurityReport;
} }
export enum MarketplaceCategory { export enum MarketplaceCategory {
...@@ -43,3 +45,30 @@ export enum MarketplaceDisplayType { ...@@ -43,3 +45,30 @@ export enum MarketplaceDisplayType {
DEFAULT = 'default', DEFAULT = 'default',
SCORES = 'scores', SCORES = 'scores',
} }
export type MarketplaceAppSecurityReport = {
overallInfo: {
verifiedNumber: number;
totalContractsNumber: number;
solidityScanContractsNumber: number;
securityScore: number;
totalIssues?: number;
issueSeverityDistribution: SolidityscanReport['scan_report']['scan_summary']['issue_severity_distribution'];
};
contractsData: [
{
address: string;
isVerified: boolean;
solidityScanReport?: SolidityscanReport['scan_report'] & {
contractname: string;
} | null;
}
];
}
export type MarketplaceAppSecurityReportRaw = {
appName: string;
chainsData: {
[chainId: string]: MarketplaceAppSecurityReport;
};
}
import { Box, Text, Link } from '@chakra-ui/react'; import { Box, Text, Link } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { MarketplaceAppSecurityReport } from 'types/client/marketplace';
import config from 'configs/app'; import config from 'configs/app';
import { apos } from 'lib/html-entities'; import { apos } from 'lib/html-entities';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
...@@ -11,7 +13,7 @@ import SolidityscanReportScore from 'ui/shared/solidityscanReport/SolidityscanRe ...@@ -11,7 +13,7 @@ import SolidityscanReportScore from 'ui/shared/solidityscanReport/SolidityscanRe
type Props = { type Props = {
id: string; id: string;
securityReport?: any; // eslint-disable-line @typescript-eslint/no-explicit-any securityReport?: MarketplaceAppSecurityReport;
height?: string | undefined; height?: string | undefined;
showContractList: () => void; showContractList: () => void;
isLoading?: boolean; isLoading?: boolean;
...@@ -37,7 +39,7 @@ const AppSecurityReport = ({ id, securityReport, height, showContractList, isLoa ...@@ -37,7 +39,7 @@ const AppSecurityReport = ({ id, securityReport, height, showContractList, isLoa
const { const {
securityScore = 0, securityScore = 0,
solidityScanContractsNumber = 0, solidityScanContractsNumber = 0,
issueSeverityDistribution = {}, issueSeverityDistribution = {} as MarketplaceAppSecurityReport['overallInfo']['issueSeverityDistribution'],
totalIssues = 0, totalIssues = 0,
} = securityReport?.overallInfo || {}; } = securityReport?.overallInfo || {};
......
...@@ -25,7 +25,7 @@ const values = { ...@@ -25,7 +25,7 @@ const values = {
}; };
interface Props { interface Props {
children: string; children: string | number;
onClick: (event: MouseEvent) => void; onClick: (event: MouseEvent) => void;
variant: ContractListButtonVariants; variant: ContractListButtonVariants;
isLoading?: boolean; isLoading?: boolean;
......
...@@ -4,6 +4,7 @@ import { ...@@ -4,6 +4,7 @@ import {
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { MarketplaceAppSecurityReport } from 'types/client/marketplace';
import { ContractListTypes } from 'types/client/marketplace'; import { ContractListTypes } from 'types/client/marketplace';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
...@@ -15,7 +16,7 @@ import ContractSecurityReport from './ContractSecurityReport'; ...@@ -15,7 +16,7 @@ import ContractSecurityReport from './ContractSecurityReport';
type Props = { type Props = {
onClose: () => void; onClose: () => void;
type: ContractListTypes; type: ContractListTypes;
contracts: Array<any>; // eslint-disable-line @typescript-eslint/no-explicit-any contracts?: MarketplaceAppSecurityReport['contractsData'];
} }
const titles = { const titles = {
...@@ -28,6 +29,9 @@ const ContractListModal = ({ onClose, type, contracts }: Props) => { ...@@ -28,6 +29,9 @@ const ContractListModal = ({ onClose, type, contracts }: Props) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const displayedContracts = React.useMemo(() => { const displayedContracts = React.useMemo(() => {
if (!contracts) {
return [];
}
switch (type) { switch (type) {
default: default:
case ContractListTypes.ALL: case ContractListTypes.ALL:
...@@ -35,12 +39,18 @@ const ContractListModal = ({ onClose, type, contracts }: Props) => { ...@@ -35,12 +39,18 @@ const ContractListModal = ({ onClose, type, contracts }: Props) => {
case ContractListTypes.ANALYZED: case ContractListTypes.ANALYZED:
return contracts return contracts
.filter((contract) => Boolean(contract.solidityScanReport)) .filter((contract) => Boolean(contract.solidityScanReport))
.sort((a, b) => b.solidityScanReport.scan_summary.score_v2 - a.solidityScanReport.scan_summary.score_v2); .sort((a, b) =>
(parseFloat(b.solidityScanReport?.scan_summary.score_v2 ?? '0')) - (parseFloat(a.solidityScanReport?.scan_summary.score_v2 ?? '0')),
);
case ContractListTypes.VERIFIED: case ContractListTypes.VERIFIED:
return contracts.filter((contract) => contract.isVerified); return contracts.filter((contract) => contract.isVerified);
} }
}, [ contracts, type ]); }, [ contracts, type ]);
if (!contracts) {
return null;
}
return ( return (
<Modal <Modal
isOpen={ Boolean(type) } isOpen={ Boolean(type) }
......
import { Box, Text } from '@chakra-ui/react'; import { Box, Text } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { SolidityscanReport } from 'types/api/contract';
import config from 'configs/app'; import config from 'configs/app';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import LinkExternal from 'ui/shared/LinkExternal'; import LinkExternal from 'ui/shared/LinkExternal';
...@@ -9,34 +11,36 @@ import SolidityscanReportDetails from 'ui/shared/solidityscanReport/Solidityscan ...@@ -9,34 +11,36 @@ import SolidityscanReportDetails from 'ui/shared/solidityscanReport/Solidityscan
import SolidityscanReportScore from 'ui/shared/solidityscanReport/SolidityscanReportScore'; import SolidityscanReportScore from 'ui/shared/solidityscanReport/SolidityscanReportScore';
type Props = { type Props = {
securityReport?: any; // eslint-disable-line @typescript-eslint/no-explicit-any securityReport?: SolidityscanReport['scan_report'] | null;
} }
const ContractSecurityReport = ({ securityReport }: Props) => { const ContractSecurityReport = ({ securityReport }: Props) => {
const handleClick = React.useCallback(() => {
mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Security score', Source: 'Analyzed contracts popup' });
}, [ ]);
if (!securityReport) {
return null;
}
const url = securityReport?.scanner_reference_url;
const { const {
scanner_reference_url: url,
scan_summary: {
score_v2: securityScore, score_v2: securityScore,
issue_severity_distribution: issueSeverityDistribution, issue_severity_distribution: issueSeverityDistribution,
}, } = securityReport.scan_summary;
} = securityReport;
const totalIssues = Object.values(issueSeverityDistribution as Record<string, number>).reduce((acc, val) => acc + val, 0); const totalIssues = Object.values(issueSeverityDistribution as Record<string, number>).reduce((acc, val) => acc + val, 0);
const handleClick = React.useCallback(() => {
mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Security score', Source: 'Analyzed contracts popup' });
}, [ ]);
return ( return (
<SolidityscanReportButton <SolidityscanReportButton
score={ securityScore } score={ parseFloat(securityScore) }
onClick={ handleClick } onClick={ handleClick }
popoverContent={ ( popoverContent={ (
<> <>
<Box mb={ 5 }> <Box mb={ 5 }>
The security score was derived from evaluating the smart contracts of a protocol on the { config.chain.name } network. The security score was derived from evaluating the smart contracts of a protocol on the { config.chain.name } network.
</Box> </Box>
<SolidityscanReportScore score={ securityScore }/> <SolidityscanReportScore score={ parseFloat(securityScore) }/>
{ issueSeverityDistribution && totalIssues > 0 && ( { issueSeverityDistribution && totalIssues > 0 && (
<Box mb={ 5 }> <Box mb={ 5 }>
<Text py="7px" variant="secondary" fontSize="xs" fontWeight={ 500 }>Threat score & vulnerabilities</Text> <Text py="7px" variant="secondary" fontSize="xs" fontWeight={ 500 }>Threat score & vulnerabilities</Text>
......
import { chakra, Flex, Tooltip, Skeleton, useBoolean, Box } from '@chakra-ui/react'; import { chakra, Flex, Tooltip, Skeleton, useBoolean, Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { MarketplaceAppOverview } from 'types/client/marketplace'; import type { MarketplaceAppOverview, MarketplaceAppSecurityReport } from 'types/client/marketplace';
import { ContractListTypes } from 'types/client/marketplace'; import { ContractListTypes } from 'types/client/marketplace';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
...@@ -22,7 +22,7 @@ type Props = { ...@@ -22,7 +22,7 @@ type Props = {
data: MarketplaceAppOverview | undefined; data: MarketplaceAppOverview | undefined;
isLoading: boolean; isLoading: boolean;
isWalletConnected: boolean; isWalletConnected: boolean;
securityReport?: any; // eslint-disable-line @typescript-eslint/no-explicit-any securityReport?: MarketplaceAppSecurityReport;
} }
const MarketplaceAppTopBar = ({ data, isLoading, isWalletConnected, securityReport }: Props) => { const MarketplaceAppTopBar = ({ data, isLoading, isWalletConnected, securityReport }: Props) => {
......
...@@ -2,7 +2,7 @@ import { Flex, IconButton, Text } from '@chakra-ui/react'; ...@@ -2,7 +2,7 @@ import { Flex, IconButton, Text } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { MouseEvent } from 'react'; import type { MouseEvent } from 'react';
import type { MarketplaceAppPreview } from 'types/client/marketplace'; import type { MarketplaceAppWithSecurityReport } from 'types/client/marketplace';
import { ContractListTypes } from 'types/client/marketplace'; import { ContractListTypes } from 'types/client/marketplace';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
...@@ -15,7 +15,7 @@ import AppLink from './AppLink'; ...@@ -15,7 +15,7 @@ import AppLink from './AppLink';
import MoreInfoButton from './MoreInfoButton'; import MoreInfoButton from './MoreInfoButton';
type Props = { type Props = {
app: MarketplaceAppPreview & { securityReport?: any }; // eslint-disable-line @typescript-eslint/no-explicit-any app: MarketplaceAppWithSecurityReport;
onInfoClick: (id: string) => void; onInfoClick: (id: string) => void;
isFavorite: boolean; isFavorite: boolean;
onFavoriteClick: (id: string, isFavorite: boolean, source: 'Security view') => void; onFavoriteClick: (id: string, isFavorite: boolean, source: 'Security view') => void;
...@@ -110,14 +110,14 @@ const ListItem = ({ app, onInfoClick, isFavorite, onFavoriteClick, isLoading, on ...@@ -110,14 +110,14 @@ const ListItem = ({ app, onInfoClick, isFavorite, onFavoriteClick, isLoading, on
variant={ ContractListButtonVariants.ALL_CONTRACTS } variant={ ContractListButtonVariants.ALL_CONTRACTS }
isLoading={ isLoading } isLoading={ isLoading }
> >
{ securityReport?.overallInfo.totalContractsNumber } { securityReport?.overallInfo.totalContractsNumber ?? 0 }
</ContractListButton> </ContractListButton>
<ContractListButton <ContractListButton
onClick={ showVerifiedContracts } onClick={ showVerifiedContracts }
variant={ ContractListButtonVariants.VERIFIED_CONTRACTS } variant={ ContractListButtonVariants.VERIFIED_CONTRACTS }
isLoading={ isLoading } isLoading={ isLoading }
> >
{ securityReport?.overallInfo.verifiedNumber } { securityReport?.overallInfo.verifiedNumber ?? 0 }
</ContractListButton> </ContractListButton>
</> </>
) : ( ) : (
......
...@@ -2,14 +2,14 @@ import { Table as ChakraTable, Tbody, Th, Tr } from '@chakra-ui/react'; ...@@ -2,14 +2,14 @@ import { Table as ChakraTable, Tbody, Th, Tr } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { MouseEvent } from 'react'; import type { MouseEvent } from 'react';
import type { MarketplaceAppPreview, ContractListTypes } from 'types/client/marketplace'; import type { MarketplaceAppWithSecurityReport, ContractListTypes } from 'types/client/marketplace';
import { default as Thead } from 'ui/shared/TheadSticky'; import { default as Thead } from 'ui/shared/TheadSticky';
import TableItem from './TableItem'; import TableItem from './TableItem';
type Props = { type Props = {
apps: Array<MarketplaceAppPreview>; apps: Array<MarketplaceAppWithSecurityReport>;
isLoading?: boolean; isLoading?: boolean;
favoriteApps: Array<string>; favoriteApps: Array<string>;
onFavoriteClick: (id: string, isFavorite: boolean, source: 'Security view') => void; onFavoriteClick: (id: string, isFavorite: boolean, source: 'Security view') => void;
......
...@@ -2,7 +2,7 @@ import { Td, Tr, IconButton, Skeleton, Text } from '@chakra-ui/react'; ...@@ -2,7 +2,7 @@ import { Td, Tr, IconButton, Skeleton, Text } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { MouseEvent } from 'react'; import type { MouseEvent } from 'react';
import type { MarketplaceAppPreview } from 'types/client/marketplace'; import type { MarketplaceAppWithSecurityReport } from 'types/client/marketplace';
import { ContractListTypes } from 'types/client/marketplace'; import { ContractListTypes } from 'types/client/marketplace';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
...@@ -14,7 +14,7 @@ import AppLink from './AppLink'; ...@@ -14,7 +14,7 @@ import AppLink from './AppLink';
import MoreInfoButton from './MoreInfoButton'; import MoreInfoButton from './MoreInfoButton';
type Props = { type Props = {
app: MarketplaceAppPreview & { securityReport?: any }; // eslint-disable-line @typescript-eslint/no-explicit-any app: MarketplaceAppWithSecurityReport;
isLoading?: boolean; isLoading?: boolean;
isFavorite: boolean; isFavorite: boolean;
onFavoriteClick: (id: string, isFavorite: boolean, source: 'Security view') => void; onFavoriteClick: (id: string, isFavorite: boolean, source: 'Security view') => void;
...@@ -98,7 +98,7 @@ const TableItem = ({ ...@@ -98,7 +98,7 @@ const TableItem = ({
variant={ ContractListButtonVariants.ALL_CONTRACTS } variant={ ContractListButtonVariants.ALL_CONTRACTS }
isLoading={ isLoading } isLoading={ isLoading }
> >
{ securityReport?.overallInfo.totalContractsNumber } { securityReport?.overallInfo.totalContractsNumber ?? 0 }
</ContractListButton> </ContractListButton>
</Td> </Td>
<Td verticalAlign="middle"> <Td verticalAlign="middle">
...@@ -107,7 +107,7 @@ const TableItem = ({ ...@@ -107,7 +107,7 @@ const TableItem = ({
variant={ ContractListButtonVariants.VERIFIED_CONTRACTS } variant={ ContractListButtonVariants.VERIFIED_CONTRACTS }
isLoading={ isLoading } isLoading={ isLoading }
> >
{ securityReport?.overallInfo.verifiedNumber } { securityReport?.overallInfo.verifiedNumber ?? 0 }
</ContractListButton> </ContractListButton>
</Td> </Td>
</> </>
......
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import type { MarketplaceAppSecurityReport, MarketplaceAppSecurityReportRaw } from 'types/client/marketplace';
import config from 'configs/app'; import config from 'configs/app';
import type { ResourceError } from 'lib/api/resources'; import type { ResourceError } from 'lib/api/resources';
import useApiFetch from 'lib/hooks/useFetch'; import useApiFetch from 'lib/hooks/useFetch';
...@@ -10,12 +12,12 @@ const securityReportsUrl = (feature.isEnabled && feature.securityReportsUrl) || ...@@ -10,12 +12,12 @@ const securityReportsUrl = (feature.isEnabled && feature.securityReportsUrl) ||
export default function useSecurityReports() { export default function useSecurityReports() {
const apiFetch = useApiFetch(); const apiFetch = useApiFetch();
return useQuery<unknown, ResourceError<unknown>, Record<string, any>>({ // eslint-disable-line @typescript-eslint/no-explicit-any return useQuery<unknown, ResourceError<unknown>, Record<string, MarketplaceAppSecurityReport>>({
queryKey: [ 'marketplace-security-reports' ], queryKey: [ 'marketplace-security-reports' ],
queryFn: async() => apiFetch(securityReportsUrl, undefined, { resource: 'marketplace-security-reports' }), queryFn: async() => apiFetch(securityReportsUrl, undefined, { resource: 'marketplace-security-reports' }),
select: (data) => { select: (data) => {
const securityReports: Record<string, any> = {}; // eslint-disable-line @typescript-eslint/no-explicit-any const securityReports: Record<string, MarketplaceAppSecurityReport> = {};
(data as Array<any>).forEach((item) => { // eslint-disable-line @typescript-eslint/no-explicit-any (data as Array<MarketplaceAppSecurityReportRaw>).forEach((item) => {
const report = item.chainsData[config.chain.id || '']; const report = item.chainsData[config.chain.id || ''];
if (report) { if (report) {
const issues: Record<string, number> = report.overallInfo.issueSeverityDistribution; const issues: Record<string, number> = report.overallInfo.issueSeverityDistribution;
......
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