Commit 884d2f21 authored by tom's avatar tom

base functionality

parent d2368c18
...@@ -44,9 +44,10 @@ NEXT_PUBLIC_AD_ADBUTLER_ON=__PLACEHOLDER_FORNEXT_PUBLIC_AD_ADBUTLER_ON__ ...@@ -44,9 +44,10 @@ NEXT_PUBLIC_AD_ADBUTLER_ON=__PLACEHOLDER_FORNEXT_PUBLIC_AD_ADBUTLER_ON__
# api config # api config
NEXT_PUBLIC_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_HOST__ NEXT_PUBLIC_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_HOST__
NEXT_PUBLIC_API_BASE_PATH=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_BASE_PATH__ NEXT_PUBLIC_API_BASE_PATH=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_BASE_PATH__
NEXT_PUBLIC_STATS_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_STATS_API_HOST__
NEXT_PUBLIC_API_PROTOCOL=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_PROTOCOL__ NEXT_PUBLIC_API_PROTOCOL=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_PROTOCOL__
NEXT_PUBLIC_API_PORT=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_PORT__ NEXT_PUBLIC_API_PORT=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_PORT__
NEXT_PUBLIC_STATS_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_STATS_API_HOST__
NEXT_PUBLIC_VISUALIZE_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_VISUALIZE_API_HOST__
# external services config # external services config
NEXT_PUBLIC_SENTRY_DSN=__PLACEHOLDER_FOR_NEXT_PUBLIC_SENTRY_DSN__ NEXT_PUBLIC_SENTRY_DSN=__PLACEHOLDER_FOR_NEXT_PUBLIC_SENTRY_DSN__
......
...@@ -96,6 +96,7 @@ The app instance could be customized by passing following variables to NodeJS en ...@@ -96,6 +96,7 @@ The app instance could be customized by passing following variables to NodeJS en
| NEXT_PUBLIC_API_HOST | `string` *(optional)* | By default the API endpoint base URL will be set as `https://blockscout.com`. If it is not the case, pass the API host in this variable | `my-host.com` | | NEXT_PUBLIC_API_HOST | `string` *(optional)* | By default the API endpoint base URL will be set as `https://blockscout.com`. If it is not the case, pass the API host in this variable | `my-host.com` |
| NEXT_PUBLIC_API_BASE_PATH | `string` *(optional)* | Base path for API endpoint url | `/poa/core` | | NEXT_PUBLIC_API_BASE_PATH | `string` *(optional)* | Base path for API endpoint url | `/poa/core` |
| NEXT_PUBLIC_STATS_API_HOST | `string` *(optional)* | Pass the Stats API host in this variable | `https://my-host.com` | | NEXT_PUBLIC_STATS_API_HOST | `string` *(optional)* | Pass the Stats API host in this variable | `https://my-host.com` |
| NEXT_PUBLIC_VISUALIZE_API_HOST | `string` *(optional)* | Pass the Visualize API host in this variable | `https://my-host.com` |
### Featured network configuration properties ### Featured network configuration properties
......
...@@ -108,6 +108,10 @@ const config = Object.freeze({ ...@@ -108,6 +108,10 @@ const config = Object.freeze({
endpoint: getEnvValue(process.env.NEXT_PUBLIC_STATS_API_HOST), endpoint: getEnvValue(process.env.NEXT_PUBLIC_STATS_API_HOST),
basePath: '', basePath: '',
}, },
visualizeApi: {
endpoint: getEnvValue(process.env.NEXT_PUBLIC_VISUALIZE_API_HOST),
basePath: '',
},
homepage: { homepage: {
charts: parseEnvJson<Array<ChainIndicatorId>>(getEnvValue(process.env.NEXT_PUBLIC_HOMEPAGE_CHARTS)) || [], charts: parseEnvJson<Array<ChainIndicatorId>>(getEnvValue(process.env.NEXT_PUBLIC_HOMEPAGE_CHARTS)) || [],
plateGradient: getEnvValue(process.env.NEXT_PUBLIC_HOMEPAGE_PLATE_GRADIENT) || plateGradient: getEnvValue(process.env.NEXT_PUBLIC_HOMEPAGE_PLATE_GRADIENT) ||
......
...@@ -13,3 +13,4 @@ NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom ...@@ -13,3 +13,4 @@ NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom
# api config # api config
NEXT_PUBLIC_API_HOST=blockscout.com NEXT_PUBLIC_API_HOST=blockscout.com
NEXT_PUBLIC_STATS_API_HOST=https://stats-test.aws-k8s.blockscout.com NEXT_PUBLIC_STATS_API_HOST=https://stats-test.aws-k8s.blockscout.com
NEXT_PUBLIC_VISUALIZE_API_HOST=http://94.131.100.174:8050
...@@ -542,6 +542,8 @@ frontend: ...@@ -542,6 +542,8 @@ frontend:
_default: blockscout.com/eth/goerli _default: blockscout.com/eth/goerli
NEXT_PUBLIC_STATS_API_HOST: NEXT_PUBLIC_STATS_API_HOST:
_default: https://stats-test.aws-k8s.blockscout.com/ _default: https://stats-test.aws-k8s.blockscout.com/
NEXT_PUBLIC_VISUALIZE_API_HOST:
_default: http://94.131.100.174:8050
NEXT_PUBLIC_APP_HOST: NEXT_PUBLIC_APP_HOST:
_default: blockscout.com/eth/goerli _default: blockscout.com/eth/goerli
NEXT_PUBLIC_LOGOUT_URL: NEXT_PUBLIC_LOGOUT_URL:
......
...@@ -382,6 +382,8 @@ frontend: ...@@ -382,6 +382,8 @@ frontend:
review: blockscout-main.test.aws-k8s.blockscout.com review: blockscout-main.test.aws-k8s.blockscout.com
NEXT_PUBLIC_STATS_API_HOST: NEXT_PUBLIC_STATS_API_HOST:
_default: https://stats-test.aws-k8s.blockscout.com/ _default: https://stats-test.aws-k8s.blockscout.com/
NEXT_PUBLIC_VISUALIZE_API_HOST:
_default: http://94.131.100.174:8050
NEXT_PUBLIC_AUTH_URL: NEXT_PUBLIC_AUTH_URL:
_default: https://blockscout-main.test.aws-k8s.blockscout.com _default: https://blockscout-main.test.aws-k8s.blockscout.com
NEXT_PUBLIC_API_BASE_PATH: NEXT_PUBLIC_API_BASE_PATH:
......
...@@ -29,6 +29,7 @@ import type { TokensResponse, TokensFilters } from 'types/api/tokens'; ...@@ -29,6 +29,7 @@ import type { TokensResponse, TokensFilters } from 'types/api/tokens';
import type { TokenTransferResponse, TokenTransferFilters } from 'types/api/tokenTransfer'; import type { TokenTransferResponse, TokenTransferFilters } from 'types/api/tokenTransfer';
import type { TransactionsResponseValidated, TransactionsResponsePending, Transaction } from 'types/api/transaction'; import type { TransactionsResponseValidated, TransactionsResponsePending, Transaction } from 'types/api/transaction';
import type { TTxsFilters } from 'types/api/txsFilters'; import type { TTxsFilters } from 'types/api/txsFilters';
import type { VisualizedContract } from 'types/api/visualization';
import type ArrayElement from 'types/utils/ArrayElement'; import type ArrayElement from 'types/utils/ArrayElement';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
...@@ -83,6 +84,13 @@ export const RESOURCES = { ...@@ -83,6 +84,13 @@ export const RESOURCES = {
basePath: appConfig.statsApi.basePath, basePath: appConfig.statsApi.basePath,
}, },
// VISUALIZATION
visualize_sol2uml: {
path: '/api/v1/solidity\\:visualize-contracts',
endpoint: appConfig.visualizeApi.endpoint,
basePath: appConfig.visualizeApi.basePath,
},
// BLOCKS, TXS // BLOCKS, TXS
blocks: { blocks: {
path: '/api/v2/blocks', path: '/api/v2/blocks',
...@@ -356,6 +364,7 @@ Q extends 'contract_methods_read' ? Array<SmartContractReadMethod> : ...@@ -356,6 +364,7 @@ Q extends 'contract_methods_read' ? Array<SmartContractReadMethod> :
Q extends 'contract_methods_read_proxy' ? Array<SmartContractReadMethod> : Q extends 'contract_methods_read_proxy' ? Array<SmartContractReadMethod> :
Q extends 'contract_methods_write' ? Array<SmartContractWriteMethod> : Q extends 'contract_methods_write' ? Array<SmartContractWriteMethod> :
Q extends 'contract_methods_write_proxy' ? Array<SmartContractWriteMethod> : Q extends 'contract_methods_write_proxy' ? Array<SmartContractWriteMethod> :
Q extends 'visualize_sol2uml' ? VisualizedContract :
never; never;
/* eslint-enable @typescript-eslint/indent */ /* eslint-enable @typescript-eslint/indent */
......
import type { NextPage } from 'next';
import Head from 'next/head';
import React from 'react';
import getNetworkTitle from 'lib/networks/getNetworkTitle';
import Sol2Uml from 'ui/pages/Sol2Uml';
const Sol2UmlPage: NextPage = () => {
const title = getNetworkTitle();
return (
<>
<Head>
<title>{ title }</title>
</Head>
<Sol2Uml/>
</>
);
};
export default Sol2UmlPage;
export { getServerSideProps } from 'lib/next/getServerSideProps';
export interface VisualizedContract {
png: string | null;
svg: string | null;
}
import { Box, chakra, Flex, Link, Text, Tooltip } from '@chakra-ui/react'; import { Box, chakra, Flex, Text, Tooltip } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { SmartContract } from 'types/api/contract'; import type { SmartContract } from 'types/api/contract';
...@@ -6,6 +6,7 @@ import type { SmartContract } from 'types/api/contract'; ...@@ -6,6 +6,7 @@ import type { SmartContract } from 'types/api/contract';
import link from 'lib/link/link'; import link from 'lib/link/link';
import CodeEditor from 'ui/shared/CodeEditor'; import CodeEditor from 'ui/shared/CodeEditor';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
import LinkInternal from 'ui/shared/LinkInternal';
interface Props { interface Props {
data: string; data: string;
...@@ -26,13 +27,13 @@ const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, addi ...@@ -26,13 +27,13 @@ const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, addi
const diagramLink = hasSol2Yml && address ? ( const diagramLink = hasSol2Yml && address ? (
<Tooltip label="Visualize contract code using Sol2Uml JS library"> <Tooltip label="Visualize contract code using Sol2Uml JS library">
<Link <LinkInternal
href={ link('visualize_sol2uml', undefined, { address }) } href={ link('visualize_sol2uml', undefined, { address }) }
ml="auto" ml="auto"
mr={ 3 } mr={ 3 }
> >
View UML diagram View UML diagram
</Link> </LinkInternal>
</Tooltip> </Tooltip>
) : null; ) : null;
......
import { Flex } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import useIsMobile from 'lib/hooks/useIsMobile';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
import Sol2UmlDiagram from 'ui/sol2uml/Sol2UmlDiagram';
const Sol2Uml = () => {
const router = useRouter();
const isMobile = useIsMobile();
const addressHash = router.query.address?.toString() || '';
return (
<Page>
<PageTitle text="Solidity UML diagram"/>
<Flex mb={ 10 }>
<span>For contract</span>
<Address ml={ 3 }>
<AddressIcon address={{ hash: addressHash, is_contract: true, implementation_name: null }}/>
<AddressLink hash={ addressHash } type="address" ml={ 2 } truncation={ isMobile ? 'constant' : 'dynamic' }/>
</Address>
</Flex>
<Sol2UmlDiagram addressHash={ addressHash }/>
</Page>
);
};
export default Sol2Uml;
import { chakra, Tooltip, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import type { SmartContract } from 'types/api/contract';
import useApiQuery from 'lib/api/useApiQuery';
import ContentLoader from 'ui/shared/ContentLoader';
interface Props {
addressHash: string;
}
function composeSources(contract: SmartContract | undefined) {
if (!contract) {
return {};
}
const additionalSources = contract.additional_sources?.reduce<Record<string, string>>((result, item) => {
result[item.file_path] = item.source_code;
return result;
}, {});
return {
[contract.file_path || 'index.sol']: contract.source_code,
...additionalSources,
};
}
const Sol2UmlDiagram = ({ addressHash }: Props) => {
const contractQuery = useApiQuery('contract', {
pathParams: { id: addressHash },
queryOptions: {
enabled: Boolean(addressHash),
refetchOnMount: false,
},
});
const umlQuery = useApiQuery('visualize_sol2uml', {
fetchParams: {
method: 'POST',
body: {
sources: composeSources(contractQuery.data),
},
},
queryOptions: {
enabled: Boolean(contractQuery.data),
refetchOnMount: false,
},
});
const imgUrl = `data:image/svg+xml;base64,${ umlQuery.data?.svg }`;
const imgFilter = useColorModeValue('invert(0)', 'invert(1)');
const handleClick = React.useCallback(() => {
const image = new Image();
image.src = imgUrl;
const newWindow = window.open(imgUrl);
newWindow?.document.write(image.outerHTML);
}, [ imgUrl ]);
if (contractQuery.isError) {
throw Error('Contract fetch error', { cause: contractQuery.error as unknown as Error });
}
if (umlQuery.isError) {
throw Error('Uml diagram fetch error', { cause: contractQuery.error as unknown as Error });
}
if (contractQuery.isLoading || umlQuery.isLoading) {
return <ContentLoader/>;
}
if (!umlQuery.data.svg) {
return <span>No data for visualization</span>;
}
return (
<Tooltip label="Click on image to zoom" placement="top">
<chakra.img
src={ `data:image/svg+xml;base64,${ umlQuery.data.svg }` }
alt={ `Contract ${ contractQuery.data.name } UML diagram` }
onClick={ handleClick }
cursor="pointer"
filter={ imgFilter }
/>
</Tooltip>
);
};
export default React.memo(Sol2UmlDiagram);
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