Commit d8eaf563 authored by isstuev's avatar isstuev

token contract tabs

parent c9cfe3cc
import React from 'react';
import type { Address } from 'types/api/address';
import notEmpty from 'lib/notEmpty';
import ContractCode from 'ui/address/contract/ContractCode';
import ContractRead from 'ui/address/contract/ContractRead';
import ContractWrite from 'ui/address/contract/ContractWrite';
export default function useContractTabs(data: Address | undefined) {
return React.useMemo(() => {
return [
{ id: 'contact_code', title: 'Code', component: <ContractCode addressHash={ data?.hash }/> },
// this is not implemented in api yet
// data?.has_decompiled_code ?
// { id: 'contact_decompiled_code', title: 'Decompiled code', component: <div>Decompiled code</div> } :
// undefined,
data?.has_methods_read ?
{ id: 'read_contract', title: 'Read contract', component: <ContractRead addressHash={ data?.hash }/> } :
undefined,
data?.has_methods_read_proxy ?
{ id: 'read_proxy', title: 'Read proxy', component: <ContractRead addressHash={ data?.hash } isProxy/> } :
undefined,
data?.has_custom_methods_read ?
{ id: 'read_custom_methods', title: 'Read custom', component: <ContractRead addressHash={ data?.hash } isCustomAbi/> } :
undefined,
data?.has_methods_write ?
{ id: 'write_contract', title: 'Write contract', component: <ContractWrite addressHash={ data?.hash }/> } :
undefined,
data?.has_methods_write_proxy ?
{ id: 'write_proxy', title: 'Write proxy', component: <ContractWrite addressHash={ data?.hash } isProxy/> } :
undefined,
data?.has_custom_methods_write ?
{ id: 'write_custom_methods', title: 'Write custom', component: <ContractWrite addressHash={ data?.hash } isCustomAbi/> } :
undefined,
].filter(notEmpty);
}, [ data ]);
}
...@@ -17,6 +17,7 @@ import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs'; ...@@ -17,6 +17,7 @@ import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
interface Props { interface Props {
tabs: Array<RoutedSubTab>; tabs: Array<RoutedSubTab>;
addressHash?: string;
} }
export const currentChain: Chain = { export const currentChain: Chain = {
...@@ -58,12 +59,12 @@ const TAB_LIST_PROPS = { ...@@ -58,12 +59,12 @@ const TAB_LIST_PROPS = {
columnGap: 3, columnGap: 3,
}; };
const AddressContract = ({ tabs }: Props) => { const AddressContract = ({ addressHash, tabs }: Props) => {
const modalZIndex = useToken<string>('zIndices', 'modal'); const modalZIndex = useToken<string>('zIndices', 'modal');
return ( return (
<WagmiConfig client={ wagmiClient }> <WagmiConfig client={ wagmiClient }>
<ContractContextProvider> <ContractContextProvider addressHash={ addressHash }>
<RoutedTabs tabs={ tabs } variant="outline" colorScheme="gray" size="sm" tabListProps={ TAB_LIST_PROPS }/> <RoutedTabs tabs={ tabs } variant="outline" colorScheme="gray" size="sm" tabListProps={ TAB_LIST_PROPS }/>
</ContractContextProvider> </ContractContextProvider>
<Web3Modal <Web3Modal
......
import { Flex, Skeleton, Button, Grid, GridItem, Text, Alert, Link, chakra, Box } from '@chakra-ui/react'; import { Flex, Skeleton, Button, Grid, GridItem, Text, Alert, Link, chakra, Box } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import React from 'react'; import React from 'react';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import getQueryParamString from 'lib/router/getQueryParamString';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
...@@ -16,6 +14,10 @@ import RawDataSnippet from 'ui/shared/RawDataSnippet'; ...@@ -16,6 +14,10 @@ import RawDataSnippet from 'ui/shared/RawDataSnippet';
import ContractSourceCode from './ContractSourceCode'; import ContractSourceCode from './ContractSourceCode';
type Props = {
addressHash?: string;
}
const InfoItem = chakra(({ label, value, className }: { label: string; value: string; className?: string }) => ( const InfoItem = chakra(({ label, value, className }: { label: string; value: string; className?: string }) => (
<GridItem display="flex" columnGap={ 6 } wordBreak="break-all" className={ className }> <GridItem display="flex" columnGap={ 6 } wordBreak="break-all" className={ className }>
<Text w="170px" flexShrink={ 0 } fontWeight={ 500 }>{ label }</Text> <Text w="170px" flexShrink={ 0 } fontWeight={ 500 }>{ label }</Text>
...@@ -23,10 +25,7 @@ const InfoItem = chakra(({ label, value, className }: { label: string; value: st ...@@ -23,10 +25,7 @@ const InfoItem = chakra(({ label, value, className }: { label: string; value: st
</GridItem> </GridItem>
)); ));
const ContractCode = () => { const ContractCode = ({ addressHash }: Props) => {
const router = useRouter();
const addressHash = getQueryParamString(router.query.hash);
const { data, isLoading, isError } = useApiQuery('contract', { const { data, isLoading, isError } = useApiQuery('contract', {
pathParams: { hash: addressHash }, pathParams: { hash: addressHash },
queryOptions: { queryOptions: {
...@@ -62,7 +61,7 @@ const ContractCode = () => { ...@@ -62,7 +61,7 @@ const ContractCode = () => {
ml="auto" ml="auto"
mr={ 3 } mr={ 3 }
as="a" as="a"
href={ route({ pathname: '/address/[hash]/contract_verification', query: { hash: addressHash } }) } href={ route({ pathname: '/address/[hash]/contract_verification', query: { hash: addressHash || '' } }) }
> >
Verify & publish Verify & publish
</Button> </Button>
...@@ -131,7 +130,9 @@ const ContractCode = () => { ...@@ -131,7 +130,9 @@ const ContractCode = () => {
<AddressLink type="address" hash={ data.verified_twin_address_hash } truncation="constant" ml={ 2 }/> <AddressLink type="address" hash={ data.verified_twin_address_hash } truncation="constant" ml={ 2 }/>
</Address> </Address>
<chakra.span mt={ 1 }>All functions displayed below are from ABI of that contract. In order to verify current contract, proceed with </chakra.span> <chakra.span mt={ 1 }>All functions displayed below are from ABI of that contract. In order to verify current contract, proceed with </chakra.span>
<LinkInternal href={ route({ pathname: '/address/[hash]/contract_verification', query: { hash: addressHash } }) }>Verify & Publish</LinkInternal> <LinkInternal href={ route({ pathname: '/address/[hash]/contract_verification', query: { hash: addressHash || "" } }) }>
Verify & Publish
</LinkInternal>
<span> page</span> <span> page</span>
</Alert> </Alert>
) } ) }
......
import { Alert, Flex } from '@chakra-ui/react'; import { Alert, Flex } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import { useAccount } from 'wagmi'; import { useAccount } from 'wagmi';
...@@ -7,7 +6,6 @@ import type { SmartContractReadMethod, SmartContractQueryMethodRead } from 'type ...@@ -7,7 +6,6 @@ import type { SmartContractReadMethod, SmartContractQueryMethodRead } from 'type
import useApiFetch from 'lib/api/useApiFetch'; import useApiFetch from 'lib/api/useApiFetch';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import getQueryParamString from 'lib/router/getQueryParamString';
import ContractMethodsAccordion from 'ui/address/contract/ContractMethodsAccordion'; import ContractMethodsAccordion from 'ui/address/contract/ContractMethodsAccordion';
import ContentLoader from 'ui/shared/ContentLoader'; import ContentLoader from 'ui/shared/ContentLoader';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
...@@ -20,17 +18,15 @@ import ContractMethodConstant from './ContractMethodConstant'; ...@@ -20,17 +18,15 @@ import ContractMethodConstant from './ContractMethodConstant';
import ContractReadResult from './ContractReadResult'; import ContractReadResult from './ContractReadResult';
interface Props { interface Props {
addressHash?: string;
isProxy?: boolean; isProxy?: boolean;
isCustomAbi?: boolean; isCustomAbi?: boolean;
} }
const ContractRead = ({ isProxy, isCustomAbi }: Props) => { const ContractRead = ({ addressHash, isProxy, isCustomAbi }: Props) => {
const router = useRouter();
const apiFetch = useApiFetch(); const apiFetch = useApiFetch();
const { address: userAddress } = useAccount(); const { address: userAddress } = useAccount();
const addressHash = getQueryParamString(router.query.hash);
const { data, isLoading, isError } = useApiQuery(isProxy ? 'contract_methods_read_proxy' : 'contract_methods_read', { const { data, isLoading, isError } = useApiQuery(isProxy ? 'contract_methods_read_proxy' : 'contract_methods_read', {
pathParams: { hash: addressHash }, pathParams: { hash: addressHash },
queryParams: { queryParams: {
......
import _capitalize from 'lodash/capitalize'; import _capitalize from 'lodash/capitalize';
import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import { useAccount, useSigner } from 'wagmi'; import { useAccount, useSigner } from 'wagmi';
...@@ -21,14 +20,12 @@ import ContractWriteResult from './ContractWriteResult'; ...@@ -21,14 +20,12 @@ import ContractWriteResult from './ContractWriteResult';
import { getNativeCoinValue, isExtendedError } from './utils'; import { getNativeCoinValue, isExtendedError } from './utils';
interface Props { interface Props {
addressHash?: string;
isProxy?: boolean; isProxy?: boolean;
isCustomAbi?: boolean; isCustomAbi?: boolean;
} }
const ContractWrite = ({ isProxy, isCustomAbi }: Props) => { const ContractWrite = ({ addressHash, isProxy, isCustomAbi }: Props) => {
const router = useRouter();
const addressHash = getQueryParamString(router.query.hash);
const { data: signer } = useSigner(); const { data: signer } = useSigner();
const { isConnected } = useAccount(); const { isConnected } = useAccount();
......
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import type { Contract } from 'ethers'; import type { Contract } from 'ethers';
import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import { useContract, useProvider, useSigner } from 'wagmi'; import { useContract, useProvider, useSigner } from 'wagmi';
...@@ -9,6 +8,7 @@ import type { Address } from 'types/api/address'; ...@@ -9,6 +8,7 @@ import type { Address } from 'types/api/address';
import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery'; import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
type ProviderProps = { type ProviderProps = {
addressHash?: string;
children: React.ReactNode; children: React.ReactNode;
} }
...@@ -24,13 +24,11 @@ const ContractContext = React.createContext<TContractContext>({ ...@@ -24,13 +24,11 @@ const ContractContext = React.createContext<TContractContext>({
custom: null, custom: null,
}); });
export function ContractContextProvider({ children }: ProviderProps) { export function ContractContextProvider({ addressHash, children }: ProviderProps) {
const router = useRouter();
const provider = useProvider(); const provider = useProvider();
const { data: signer } = useSigner(); const { data: signer } = useSigner();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const addressHash = router.query.hash?.toString();
const { data: contractInfo } = useApiQuery('contract', { const { data: contractInfo } = useApiQuery('contract', {
pathParams: { hash: addressHash }, pathParams: { hash: addressHash },
queryOptions: { queryOptions: {
......
...@@ -8,6 +8,7 @@ import type { RoutedTab } from 'ui/shared/RoutedTabs/types'; ...@@ -8,6 +8,7 @@ import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import iconSuccess from 'icons/status/success.svg'; import iconSuccess from 'icons/status/success.svg';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext'; import { useAppContext } from 'lib/appContext';
import useContractTabs from 'lib/hooks/useContractTabs';
import notEmpty from 'lib/notEmpty'; import notEmpty from 'lib/notEmpty';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import AddressBlocksValidated from 'ui/address/AddressBlocksValidated'; import AddressBlocksValidated from 'ui/address/AddressBlocksValidated';
...@@ -19,9 +20,6 @@ import AddressLogs from 'ui/address/AddressLogs'; ...@@ -19,9 +20,6 @@ import AddressLogs from 'ui/address/AddressLogs';
import AddressTokens from 'ui/address/AddressTokens'; import AddressTokens from 'ui/address/AddressTokens';
import AddressTokenTransfers from 'ui/address/AddressTokenTransfers'; import AddressTokenTransfers from 'ui/address/AddressTokenTransfers';
import AddressTxs from 'ui/address/AddressTxs'; import AddressTxs from 'ui/address/AddressTxs';
import ContractCode from 'ui/address/contract/ContractCode';
import ContractRead from 'ui/address/contract/ContractRead';
import ContractWrite from 'ui/address/contract/ContractWrite';
import AdBanner from 'ui/shared/ad/AdBanner'; import AdBanner from 'ui/shared/ad/AdBanner';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import Page from 'ui/shared/Page/Page'; import Page from 'ui/shared/Page/Page';
...@@ -58,33 +56,7 @@ const AddressPageContent = () => { ...@@ -58,33 +56,7 @@ const AddressPageContent = () => {
...(addressQuery.data?.watchlist_names || []), ...(addressQuery.data?.watchlist_names || []),
].map((tag) => <Tag key={ tag.label }>{ tag.display_name }</Tag>); ].map((tag) => <Tag key={ tag.label }>{ tag.display_name }</Tag>);
const contractTabs = React.useMemo(() => { const contractTabs = useContractTabs(addressQuery.data);
return [
{ id: 'contact_code', title: 'Code', component: <ContractCode/> },
// this is not implemented in api yet
// addressQuery.data?.has_decompiled_code ?
// { id: 'contact_decompiled_code', title: 'Decompiled code', component: <div>Decompiled code</div> } :
// undefined,
addressQuery.data?.has_methods_read ?
{ id: 'read_contract', title: 'Read contract', component: <ContractRead/> } :
undefined,
addressQuery.data?.has_methods_read_proxy ?
{ id: 'read_proxy', title: 'Read proxy', component: <ContractRead isProxy/> } :
undefined,
addressQuery.data?.has_custom_methods_read ?
{ id: 'read_custom_methods', title: 'Read custom', component: <ContractRead isCustomAbi/> } :
undefined,
addressQuery.data?.has_methods_write ?
{ id: 'write_contract', title: 'Write contract', component: <ContractWrite/> } :
undefined,
addressQuery.data?.has_methods_write_proxy ?
{ id: 'write_proxy', title: 'Write proxy', component: <ContractWrite isProxy/> } :
undefined,
addressQuery.data?.has_custom_methods_write ?
{ id: 'write_custom_methods', title: 'Write custom', component: <ContractWrite isCustomAbi/> } :
undefined,
].filter(notEmpty);
}, [ addressQuery.data ]);
const tabs: Array<RoutedTab> = React.useMemo(() => { const tabs: Array<RoutedTab> = React.useMemo(() => {
return [ return [
...@@ -113,11 +85,11 @@ const AddressPageContent = () => { ...@@ -113,11 +85,11 @@ const AddressPageContent = () => {
return 'Contract'; return 'Contract';
}, },
component: <AddressContract tabs={ contractTabs }/>, component: <AddressContract tabs={ contractTabs } addressHash={ hash }/>,
subTabs: contractTabs.map(tab => tab.id), subTabs: contractTabs.map(tab => tab.id),
} : undefined, } : undefined,
].filter(notEmpty); ].filter(notEmpty);
}, [ addressQuery.data, contractTabs ]); }, [ addressQuery.data, contractTabs, hash ]);
const tagsNode = tags.length > 0 ? <Flex columnGap={ 2 }>{ tags }</Flex> : null; const tagsNode = tags.length > 0 ? <Flex columnGap={ 2 }>{ tags }</Flex> : null;
......
import { Skeleton, Box, Flex, SkeletonCircle } from '@chakra-ui/react'; import { Skeleton, Box, Flex, SkeletonCircle, Icon } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import type { RoutedTab } from 'ui/shared/RoutedTabs/types'; import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import iconSuccess from 'icons/status/success.svg';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext'; import { useAppContext } from 'lib/appContext';
import useContractTabs from 'lib/hooks/useContractTabs';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages'; import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import notEmpty from 'lib/notEmpty';
import trimTokenSymbol from 'lib/token/trimTokenSymbol'; import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import AddressContract from 'ui/address/AddressContract';
import AdBanner from 'ui/shared/ad/AdBanner'; import AdBanner from 'ui/shared/ad/AdBanner';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import Page from 'ui/shared/Page/Page'; import Page from 'ui/shared/Page/Page';
...@@ -36,8 +40,10 @@ const TokenPageContent = () => { ...@@ -36,8 +40,10 @@ const TokenPageContent = () => {
const scrollRef = React.useRef<HTMLDivElement>(null); const scrollRef = React.useRef<HTMLDivElement>(null);
const hashString = router.query.hash?.toString();
const tokenQuery = useApiQuery('token', { const tokenQuery = useApiQuery('token', {
pathParams: { hash: router.query.hash?.toString() }, pathParams: { hash: hashString },
queryOptions: { enabled: Boolean(router.query.hash) }, queryOptions: { enabled: Boolean(router.query.hash) },
}); });
...@@ -58,7 +64,7 @@ const TokenPageContent = () => { ...@@ -58,7 +64,7 @@ const TokenPageContent = () => {
const transfersQuery = useQueryWithPages({ const transfersQuery = useQueryWithPages({
resourceName: 'token_transfers', resourceName: 'token_transfers',
pathParams: { hash: router.query.hash?.toString() }, pathParams: { hash: hashString },
scrollRef, scrollRef,
options: { options: {
enabled: Boolean(router.query.hash && (!router.query.tab || router.query.tab === 'token_transfers') && tokenQuery.data), enabled: Boolean(router.query.hash && (!router.query.tab || router.query.tab === 'token_transfers') && tokenQuery.data),
...@@ -67,7 +73,7 @@ const TokenPageContent = () => { ...@@ -67,7 +73,7 @@ const TokenPageContent = () => {
const holdersQuery = useQueryWithPages({ const holdersQuery = useQueryWithPages({
resourceName: 'token_holders', resourceName: 'token_holders',
pathParams: { hash: router.query.hash?.toString() }, pathParams: { hash: hashString },
scrollRef, scrollRef,
options: { options: {
enabled: Boolean(router.query.hash && router.query.tab === 'holders' && tokenQuery.data), enabled: Boolean(router.query.hash && router.query.tab === 'holders' && tokenQuery.data),
...@@ -76,21 +82,44 @@ const TokenPageContent = () => { ...@@ -76,21 +82,44 @@ const TokenPageContent = () => {
const inventoryQuery = useQueryWithPages({ const inventoryQuery = useQueryWithPages({
resourceName: 'token_inventory', resourceName: 'token_inventory',
pathParams: { hash: router.query.hash?.toString() }, pathParams: { hash: hashString },
scrollRef, scrollRef,
options: { options: {
enabled: Boolean(router.query.hash && router.query.tab === 'inventory' && tokenQuery.data), enabled: Boolean(router.query.hash && router.query.tab === 'inventory' && tokenQuery.data),
}, },
}); });
const contractQuery = useApiQuery('address', {
pathParams: { id: hashString },
queryOptions: { enabled: Boolean(router.query.hash) },
});
const contractTabs = useContractTabs(contractQuery.data);
const tabs: Array<RoutedTab> = [ const tabs: Array<RoutedTab> = [
{ id: 'token_transfers', title: 'Token transfers', component: <TokenTransfer transfersQuery={ transfersQuery }/> }, { id: 'token_transfers', title: 'Token transfers', component: <TokenTransfer transfersQuery={ transfersQuery }/> },
{ id: 'holders', title: 'Holders', component: <TokenHolders tokenQuery={ tokenQuery } holdersQuery={ holdersQuery }/> }, { id: 'holders', title: 'Holders', component: <TokenHolders tokenQuery={ tokenQuery } holdersQuery={ holdersQuery }/> },
]; (tokenQuery.data?.type === 'ERC-1155' || tokenQuery.data?.type === 'ERC-721') ?
{ id: 'inventory', title: 'Inventory', component: <TokenInventory inventoryQuery={ inventoryQuery }/> } :
if (tokenQuery.data?.type === 'ERC-1155' || tokenQuery.data?.type === 'ERC-721') { undefined,
tabs.push({ id: 'inventory', title: 'Inventory', component: <TokenInventory inventoryQuery={ inventoryQuery }/> }); contractQuery.data?.is_contract ? {
} id: 'contract',
title: () => {
if (contractQuery.data.is_verified) {
return (
<>
<span>Contract</span>
<Icon as={ iconSuccess } boxSize="14px" color="green.500" ml={ 1 }/>
</>
);
}
return 'Contract';
},
component: <AddressContract tabs={ contractTabs } addressHash={ hashString }/>,
subTabs: contractTabs.map(tab => tab.id),
} : undefined,
].filter(notEmpty);
let hasPagination; let hasPagination;
let pagination; let pagination;
...@@ -138,7 +167,7 @@ const TokenPageContent = () => { ...@@ -138,7 +167,7 @@ const TokenPageContent = () => {
{ /* should stay before tabs to scroll up whith pagination */ } { /* should stay before tabs to scroll up whith pagination */ }
<Box ref={ scrollRef }></Box> <Box ref={ scrollRef }></Box>
{ tokenQuery.isLoading ? <SkeletonTabs/> : ( { tokenQuery.isLoading || contractQuery.isLoading ? <SkeletonTabs/> : (
<RoutedTabs <RoutedTabs
tabs={ tabs } tabs={ tabs }
tabListProps={ isMobile ? { mt: 8 } : { mt: 3, py: 5, marginBottom: 0 } } tabListProps={ isMobile ? { mt: 8 } : { mt: 3, py: 5, marginBottom: 0 } }
......
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