Commit b5d6ec96 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Support custom MUD system ABI (#2296)

* MUD system tab placeholder

* implement MUD system selector and fix proxy contract read/write

* rollback changes of target contract address
parent 9337b034
...@@ -67,6 +67,8 @@ import type { ...@@ -67,6 +67,8 @@ import type {
SmartContract, SmartContract,
SmartContractVerificationConfigRaw, SmartContractVerificationConfigRaw,
SmartContractSecurityAudits, SmartContractSecurityAudits,
SmartContractMudSystemsResponse,
SmartContractMudSystemInfo,
} from 'types/api/contract'; } from 'types/api/contract';
import type { VerifiedContractsResponse, VerifiedContractsFilters, VerifiedContractsCounters } from 'types/api/contracts'; import type { VerifiedContractsResponse, VerifiedContractsFilters, VerifiedContractsCounters } from 'types/api/contracts';
import type { import type {
...@@ -767,6 +769,16 @@ export const RESOURCES = { ...@@ -767,6 +769,16 @@ export const RESOURCES = {
pathParams: [ 'hash' as const, 'table_id' as const, 'record_id' as const ], pathParams: [ 'hash' as const, 'table_id' as const, 'record_id' as const ],
}, },
contract_mud_systems: {
path: '/api/v2/mud/worlds/:hash/systems',
pathParams: [ 'hash' as const ],
},
contract_mud_system_info: {
path: '/api/v2/mud/worlds/:hash/systems/:system_address',
pathParams: [ 'hash' as const, 'system_address' as const ],
},
// arbitrum L2 // arbitrum L2
arbitrum_l2_messages: { arbitrum_l2_messages: {
path: '/api/v2/arbitrum/messages/:direction', path: '/api/v2/arbitrum/messages/:direction',
...@@ -1195,6 +1207,8 @@ Q extends 'address_mud_tables' ? AddressMudTables : ...@@ -1195,6 +1207,8 @@ Q extends 'address_mud_tables' ? AddressMudTables :
Q extends 'address_mud_tables_count' ? number : Q extends 'address_mud_tables_count' ? number :
Q extends 'address_mud_records' ? AddressMudRecords : Q extends 'address_mud_records' ? AddressMudRecords :
Q extends 'address_mud_record' ? AddressMudRecord : Q extends 'address_mud_record' ? AddressMudRecord :
Q extends 'contract_mud_systems' ? SmartContractMudSystemsResponse :
Q extends 'contract_mud_system_info' ? SmartContractMudSystemInfo :
Q extends 'address_epoch_rewards' ? AddressEpochRewardsResponse : Q extends 'address_epoch_rewards' ? AddressEpochRewardsResponse :
Q extends 'withdrawals' ? WithdrawalsResponse : Q extends 'withdrawals' ? WithdrawalsResponse :
Q extends 'withdrawals_counters' ? WithdrawalsCounters : Q extends 'withdrawals_counters' ? WithdrawalsCounters :
......
...@@ -10,9 +10,11 @@ import useSocketChannel from 'lib/socket/useSocketChannel'; ...@@ -10,9 +10,11 @@ import useSocketChannel from 'lib/socket/useSocketChannel';
import * as stubs from 'stubs/contract'; import * as stubs from 'stubs/contract';
import ContractCode from 'ui/address/contract/ContractCode'; import ContractCode from 'ui/address/contract/ContractCode';
import ContractMethodsCustom from 'ui/address/contract/methods/ContractMethodsCustom'; import ContractMethodsCustom from 'ui/address/contract/methods/ContractMethodsCustom';
import ContractMethodsMudSystem from 'ui/address/contract/methods/ContractMethodsMudSystem';
import ContractMethodsProxy from 'ui/address/contract/methods/ContractMethodsProxy'; import ContractMethodsProxy from 'ui/address/contract/methods/ContractMethodsProxy';
import ContractMethodsRegular from 'ui/address/contract/methods/ContractMethodsRegular'; import ContractMethodsRegular from 'ui/address/contract/methods/ContractMethodsRegular';
import { divideAbiIntoMethodTypes } from 'ui/address/contract/methods/utils'; import { divideAbiIntoMethodTypes } from 'ui/address/contract/methods/utils';
import ContentLoader from 'ui/shared/ContentLoader';
const CONTRACT_TAB_IDS = [ const CONTRACT_TAB_IDS = [
'contract_code', 'contract_code',
...@@ -24,6 +26,7 @@ const CONTRACT_TAB_IDS = [ ...@@ -24,6 +26,7 @@ const CONTRACT_TAB_IDS = [
'write_contract_rpc', 'write_contract_rpc',
'write_proxy', 'write_proxy',
'write_custom_methods', 'write_custom_methods',
'mud_system',
] as const; ] as const;
interface ContractTab { interface ContractTab {
...@@ -37,7 +40,7 @@ interface ReturnType { ...@@ -37,7 +40,7 @@ interface ReturnType {
isLoading: boolean; isLoading: boolean;
} }
export default function useContractTabs(data: Address | undefined, isPlaceholderData: boolean): ReturnType { export default function useContractTabs(data: Address | undefined, isPlaceholderData: boolean, hasMudTab?: boolean): ReturnType {
const [ isQueryEnabled, setIsQueryEnabled ] = React.useState(false); const [ isQueryEnabled, setIsQueryEnabled ] = React.useState(false);
const router = useRouter(); const router = useRouter();
...@@ -65,6 +68,15 @@ export default function useContractTabs(data: Address | undefined, isPlaceholder ...@@ -65,6 +68,15 @@ export default function useContractTabs(data: Address | undefined, isPlaceholder
}, },
}); });
const mudSystemsQuery = useApiQuery('contract_mud_systems', {
pathParams: { hash: data?.hash },
queryOptions: {
enabled: isEnabled && isQueryEnabled && hasMudTab,
refetchOnMount: false,
placeholderData: stubs.MUD_SYSTEMS,
},
});
const channel = useSocketChannel({ const channel = useSocketChannel({
topic: `addresses:${ data?.hash?.toLowerCase() }`, topic: `addresses:${ data?.hash?.toLowerCase() }`,
isDisabled: !isEnabled, isDisabled: !isEnabled,
...@@ -136,8 +148,26 @@ export default function useContractTabs(data: Address | undefined, isPlaceholder ...@@ -136,8 +148,26 @@ export default function useContractTabs(data: Address | undefined, isPlaceholder
/> />
), ),
}, },
hasMudTab && {
id: 'mud_system' as const,
title: 'MUD System',
component: mudSystemsQuery.isPlaceholderData ?
<ContentLoader/> :
<ContractMethodsMudSystem items={ mudSystemsQuery.data?.items ?? [] }/>,
},
].filter(Boolean), ].filter(Boolean),
isLoading: contractQuery.isPlaceholderData, isLoading: contractQuery.isPlaceholderData,
}; };
}, [ contractQuery, channel, data?.hash, verifiedImplementations, methods.read, methods.write, methodsCustomAbi.read, methodsCustomAbi.write ]); }, [
contractQuery,
channel,
data?.hash,
methods.read,
methods.write,
methodsCustomAbi.read,
methodsCustomAbi.write,
verifiedImplementations,
mudSystemsQuery,
hasMudTab,
]);
} }
import type { SmartContract } from 'types/api/contract'; import type { SmartContract, SmartContractMudSystemsResponse } from 'types/api/contract';
import type { VerifiedContract, VerifiedContractsCounters } from 'types/api/contracts'; import type { VerifiedContract, VerifiedContractsCounters } from 'types/api/contracts';
import type { SolidityScanReport } from 'lib/solidityScan/schema'; import type { SolidityScanReport } from 'lib/solidityScan/schema';
import { ADDRESS_PARAMS } from './addressParams'; import { ADDRESS_PARAMS, ADDRESS_HASH } from './addressParams';
export const CONTRACT_CODE_UNVERIFIED = { export const CONTRACT_CODE_UNVERIFIED = {
creation_bytecode: '0x60806040526e', creation_bytecode: '0x60806040526e',
...@@ -98,3 +98,12 @@ export const SOLIDITY_SCAN_REPORT: SolidityScanReport = { ...@@ -98,3 +98,12 @@ export const SOLIDITY_SCAN_REPORT: SolidityScanReport = {
scanner_reference_url: 'https://solidityscan.com/quickscan/0xc1EF7811FF2ebFB74F80ed7423f2AdAA37454be2/blockscout/eth-goerli?ref=blockscout', scanner_reference_url: 'https://solidityscan.com/quickscan/0xc1EF7811FF2ebFB74F80ed7423f2AdAA37454be2/blockscout/eth-goerli?ref=blockscout',
}, },
}; };
export const MUD_SYSTEMS: SmartContractMudSystemsResponse = {
items: [
{
name: 'sy.AccessManagement',
address: ADDRESS_HASH,
},
],
};
...@@ -143,3 +143,19 @@ export type SmartContractSecurityAuditSubmission = { ...@@ -143,3 +143,19 @@ export type SmartContractSecurityAuditSubmission = {
'audit_publish_date': string; 'audit_publish_date': string;
'comment'?: string; 'comment'?: string;
} }
// MUD SYSTEM
export interface SmartContractMudSystemsResponse {
items: Array<SmartContractMudSystemItem>;
}
export interface SmartContractMudSystemItem {
address: string;
name: string;
}
export interface SmartContractMudSystemInfo {
name: string;
abi: Abi;
}
...@@ -12,9 +12,10 @@ interface Props { ...@@ -12,9 +12,10 @@ interface Props {
abi: Array<SmartContractMethod>; abi: Array<SmartContractMethod>;
addressHash: string; addressHash: string;
tab: string; tab: string;
sourceAddress?: string;
} }
const ContractAbi = ({ abi, addressHash, tab }: Props) => { const ContractAbi = ({ abi, addressHash, sourceAddress, tab }: Props) => {
const [ expandedSections, setExpandedSections ] = React.useState<Array<number>>(abi.length === 1 ? [ 0 ] : []); const [ expandedSections, setExpandedSections ] = React.useState<Array<number>>(abi.length === 1 ? [ 0 ] : []);
const [ id, setId ] = React.useState(0); const [ id, setId ] = React.useState(0);
...@@ -61,6 +62,7 @@ const ContractAbi = ({ abi, addressHash, tab }: Props) => { ...@@ -61,6 +62,7 @@ const ContractAbi = ({ abi, addressHash, tab }: Props) => {
id={ id } id={ id }
index={ index } index={ index }
addressHash={ addressHash } addressHash={ addressHash }
sourceAddress={ sourceAddress }
tab={ tab } tab={ tab }
onSubmit={ handleFormSubmit } onSubmit={ handleFormSubmit }
/> />
......
...@@ -19,11 +19,12 @@ interface Props { ...@@ -19,11 +19,12 @@ interface Props {
index: number; index: number;
id: number; id: number;
addressHash: string; addressHash: string;
sourceAddress?: string;
tab: string; tab: string;
onSubmit: FormSubmitHandler; onSubmit: FormSubmitHandler;
} }
const ContractAbiItem = ({ data, index, id, addressHash, tab, onSubmit }: Props) => { const ContractAbiItem = ({ data, index, id, addressHash, sourceAddress, tab, onSubmit }: Props) => {
const [ attempt, setAttempt ] = React.useState(0); const [ attempt, setAttempt ] = React.useState(0);
const url = React.useMemo(() => { const url = React.useMemo(() => {
...@@ -36,10 +37,11 @@ const ContractAbiItem = ({ data, index, id, addressHash, tab, onSubmit }: Props) ...@@ -36,10 +37,11 @@ const ContractAbiItem = ({ data, index, id, addressHash, tab, onSubmit }: Props)
query: { query: {
hash: addressHash ?? '', hash: addressHash ?? '',
tab, tab,
...(sourceAddress ? { source_address: sourceAddress } : {}),
}, },
hash: data.method_id, hash: data.method_id,
}); });
}, [ addressHash, data, tab ]); }, [ addressHash, data, tab, sourceAddress ]);
const handleCopyLinkClick = React.useCallback((event: React.MouseEvent) => { const handleCopyLinkClick = React.useCallback((event: React.MouseEvent) => {
event.stopPropagation(); event.stopPropagation();
......
...@@ -14,9 +14,10 @@ interface Props { ...@@ -14,9 +14,10 @@ interface Props {
isLoading?: boolean; isLoading?: boolean;
isError?: boolean; isError?: boolean;
type: MethodType; type: MethodType;
sourceAddress?: string;
} }
const ContractMethods = ({ abi, isLoading, isError, type }: Props) => { const ContractMethods = ({ abi, isLoading, isError, type, sourceAddress }: Props) => {
const router = useRouter(); const router = useRouter();
...@@ -32,10 +33,11 @@ const ContractMethods = ({ abi, isLoading, isError, type }: Props) => { ...@@ -32,10 +33,11 @@ const ContractMethods = ({ abi, isLoading, isError, type }: Props) => {
} }
if (abi.length === 0) { if (abi.length === 0) {
return <span>No public { type } functions were found for this contract.</span>; const typeText = type === 'all' ? '' : type;
return <span>No public { typeText } functions were found for this contract.</span>;
} }
return <ContractAbi abi={ abi } tab={ tab } addressHash={ addressHash }/>; return <ContractAbi abi={ abi } tab={ tab } addressHash={ addressHash } sourceAddress={ sourceAddress }/>;
}; };
export default React.memo(ContractMethods); export default React.memo(ContractMethods);
import { Box } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import type { SmartContractMudSystemItem } from 'types/api/contract';
import useApiQuery from 'lib/api/useApiQuery';
import getQueryParamString from 'lib/router/getQueryParamString';
import ContractConnectWallet from './ContractConnectWallet';
import ContractMethods from './ContractMethods';
import type { Item } from './ContractSourceAddressSelector';
import ContractSourceAddressSelector from './ContractSourceAddressSelector';
import { enrichWithMethodId, isMethod } from './utils';
interface Props {
items: Array<SmartContractMudSystemItem>;
}
const ContractMethodsMudSystem = ({ items }: Props) => {
const router = useRouter();
const addressHash = getQueryParamString(router.query.hash);
const contractAddress = getQueryParamString(router.query.source_address);
const [ selectedItem, setSelectedItem ] = React.useState(items.find((item) => item.address === contractAddress) || items[0]);
const systemInfoQuery = useApiQuery('contract_mud_system_info', {
pathParams: { hash: addressHash, system_address: selectedItem.address },
queryOptions: {
enabled: Boolean(selectedItem?.address),
refetchOnMount: false,
},
});
const handleItemSelect = React.useCallback((item: Item) => {
setSelectedItem(item as SmartContractMudSystemItem);
}, []);
if (items.length === 0) {
return <span>No MUD System found for this contract.</span>;
}
const abi = systemInfoQuery.data?.abi?.filter(isMethod).map(enrichWithMethodId) || [];
return (
<Box>
<ContractConnectWallet/>
<ContractSourceAddressSelector
items={ items }
selectedItem={ selectedItem }
onItemSelect={ handleItemSelect }
label="System address"
/>
<ContractMethods
key={ selectedItem.address }
abi={ abi }
isLoading={ systemInfoQuery.isPending }
isError={ systemInfoQuery.isError }
sourceAddress={ selectedItem.address }
type="all"
/>
</Box>
);
};
export default React.memo(ContractMethodsMudSystem);
import { Box } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import type { MethodType } from './types'; import type { MethodType } from './types';
import type { AddressImplementation } from 'types/api/addressParams'; import type { AddressImplementation } from 'types/api/addressParams';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import getQueryParamString from 'lib/router/getQueryParamString';
import ContractConnectWallet from './ContractConnectWallet'; import ContractConnectWallet from './ContractConnectWallet';
import ContractImplementationAddress from './ContractImplementationAddress';
import ContractMethods from './ContractMethods'; import ContractMethods from './ContractMethods';
import { isReadMethod, isWriteMethod } from './utils'; import ContractSourceAddressSelector from './ContractSourceAddressSelector';
import { enrichWithMethodId, isReadMethod, isWriteMethod } from './utils';
interface Props { interface Props {
type: MethodType; type: MethodType;
...@@ -18,8 +20,10 @@ interface Props { ...@@ -18,8 +20,10 @@ interface Props {
} }
const ContractMethodsProxy = ({ type, implementations, isLoading: isInitialLoading }: Props) => { const ContractMethodsProxy = ({ type, implementations, isLoading: isInitialLoading }: Props) => {
const router = useRouter();
const contractAddress = getQueryParamString(router.query.source_address);
const [ selectedItem, setSelectedItem ] = React.useState(implementations[0]); const [ selectedItem, setSelectedItem ] = React.useState(implementations.find((item) => item.address === contractAddress) || implementations[0]);
const contractQuery = useApiQuery('contract', { const contractQuery = useApiQuery('contract', {
pathParams: { hash: selectedItem.address }, pathParams: { hash: selectedItem.address },
...@@ -29,29 +33,24 @@ const ContractMethodsProxy = ({ type, implementations, isLoading: isInitialLoadi ...@@ -29,29 +33,24 @@ const ContractMethodsProxy = ({ type, implementations, isLoading: isInitialLoadi
}, },
}); });
const handleItemSelect = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => { const abi = contractQuery.data?.abi?.filter(type === 'read' ? isReadMethod : isWriteMethod).map(enrichWithMethodId) || [];
const nextOption = implementations.find(({ address }) => address === event.target.value);
if (nextOption) {
setSelectedItem(nextOption);
}
}, [ implementations ]);
const abi = contractQuery.data?.abi?.filter(type === 'read' ? isReadMethod : isWriteMethod) || [];
return ( return (
<Box> <Box>
<ContractConnectWallet isLoading={ isInitialLoading }/> <ContractConnectWallet isLoading={ isInitialLoading }/>
<ContractImplementationAddress <ContractSourceAddressSelector
implementations={ implementations } items={ implementations }
selectedItem={ selectedItem } selectedItem={ selectedItem }
onItemSelect={ handleItemSelect } onItemSelect={ setSelectedItem }
isLoading={ isInitialLoading } isLoading={ isInitialLoading }
label="Implementation address"
/> />
<ContractMethods <ContractMethods
key={ selectedItem.address } key={ selectedItem.address }
abi={ abi } abi={ abi }
isLoading={ isInitialLoading || contractQuery.isPending } isLoading={ isInitialLoading || contractQuery.isPending }
isError={ contractQuery.isError } isError={ contractQuery.isError }
sourceAddress={ selectedItem.address }
type={ type } type={ type }
/> />
</Box> </Box>
......
import { Flex, Select, Skeleton } from '@chakra-ui/react'; import { Flex, Select, Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { AddressImplementation } from 'types/api/addressParams';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import LinkNewTab from 'ui/shared/links/LinkNewTab'; import LinkNewTab from 'ui/shared/links/LinkNewTab';
export interface Item {
address: string;
name?: string | null | undefined;
}
interface Props { interface Props {
selectedItem: AddressImplementation; label: string;
onItemSelect: (event: React.ChangeEvent<HTMLSelectElement>) => void; selectedItem: Item;
implementations: Array<AddressImplementation>; onItemSelect: (item: Item) => void;
items: Array<Item>;
isLoading?: boolean; isLoading?: boolean;
} }
const ContractImplementationAddress = ({ selectedItem, onItemSelect, implementations, isLoading }: Props) => { const ContractSourceAddressSelector = ({ selectedItem, onItemSelect, items, isLoading, label }: Props) => {
const handleItemSelect = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => {
const nextOption = items.find(({ address }) => address === event.target.value);
if (nextOption) {
onItemSelect(nextOption);
}
}, [ items, onItemSelect ]);
if (isLoading) { if (isLoading) {
return <Skeleton mb={ 6 } h={ 6 } w={{ base: '300px', lg: '500px' }}/>; return <Skeleton mb={ 6 } h={ 6 } w={{ base: '300px', lg: '500px' }}/>;
} }
if (implementations.length === 0) { if (items.length === 0) {
return null; return null;
} }
if (implementations.length === 1) { if (items.length === 1) {
return ( return (
<Flex mb={ 6 } flexWrap="wrap" columnGap={ 2 } rowGap={ 2 }> <Flex mb={ 6 } flexWrap="wrap" columnGap={ 3 } rowGap={ 2 }>
<span>Implementation address:</span> <span>{ label }</span>
<AddressEntity <AddressEntity
address={{ hash: implementations[0].address, is_contract: true, is_verified: true }} address={{ hash: items[0].address, is_contract: true, is_verified: true }}
/> />
</Flex> </Flex>
); );
} }
return ( return (
<Flex mb={ 6 } flexWrap="wrap" columnGap={ 2 } rowGap={ 2 } alignItems="center"> <Flex mb={ 6 } flexWrap="wrap" columnGap={ 3 } rowGap={ 2 } alignItems="center">
<span>Implementation address:</span> <span>{ label }</span>
<Select <Select
size="xs" size="xs"
value={ selectedItem.address } value={ selectedItem.address }
onChange={ onItemSelect } onChange={ handleItemSelect }
w="auto" w="auto"
fontWeight={ 600 } fontWeight={ 600 }
borderRadius="base" borderRadius="base"
> >
{ implementations.map((implementation) => ( { items.map((item) => (
<option key={ implementation.address } value={ implementation.address }> <option key={ item.address } value={ item.address }>
{ implementation.name } { item.name }
</option> </option>
)) } )) }
</Select> </Select>
<CopyToClipboard text={ selectedItem.address } ml={ 1 }/> <Flex columnGap={ 2 } alignItems="center">
<LinkNewTab <CopyToClipboard text={ selectedItem.address } ml={ 0 }/>
label="Open contract details page in new tab" <LinkNewTab
href={ route({ pathname: '/address/[hash]', query: { hash: selectedItem.address, tab: 'contract' } }) } label="Open contract details page in new tab"
/> href={ route({ pathname: '/address/[hash]', query: { hash: selectedItem.address, tab: 'contract' } }) }
/>
</Flex>
</Flex> </Flex>
); );
}; };
export default React.memo(ContractImplementationAddress); export default React.memo(ContractSourceAddressSelector);
...@@ -2,7 +2,7 @@ import type { AbiFunction, AbiFallback, AbiReceive } from 'abitype'; ...@@ -2,7 +2,7 @@ import type { AbiFunction, AbiFallback, AbiReceive } from 'abitype';
export type ContractAbiItemInput = AbiFunction['inputs'][number] & { fieldType?: 'native_coin' }; export type ContractAbiItemInput = AbiFunction['inputs'][number] & { fieldType?: 'native_coin' };
export type MethodType = 'read' | 'write'; export type MethodType = 'read' | 'write' | 'all';
export type MethodCallStrategy = 'read' | 'write' | 'simulate'; export type MethodCallStrategy = 'read' | 'write' | 'simulate';
export type ResultViewMode = 'preview' | 'result'; export type ResultViewMode = 'preview' | 'result';
......
import type { Abi } from 'abitype'; import type { Abi, AbiFallback, AbiReceive } from 'abitype';
import type { AbiFunction } from 'viem'; import type { AbiFunction } from 'viem';
import { toFunctionSelector } from 'viem'; import { toFunctionSelector } from 'viem';
import type { SmartContractMethodCustomFields, SmartContractMethodRead, SmartContractMethodWrite } from './types'; import type { SmartContractMethod, SmartContractMethodRead, SmartContractMethodWrite } from './types';
export const getNativeCoinValue = (value: unknown) => { export const getNativeCoinValue = (value: unknown) => {
if (typeof value !== 'string') { if (typeof value !== 'string') {
...@@ -17,6 +17,9 @@ interface DividedAbi { ...@@ -17,6 +17,9 @@ interface DividedAbi {
write: Array<SmartContractMethodWrite>; write: Array<SmartContractMethodWrite>;
} }
export const isMethod = (method: Abi[number]): method is SmartContractMethod =>
(method.type === 'function' || method.type === 'fallback' || method.type === 'receive');
export const isReadMethod = (method: Abi[number]): method is SmartContractMethodRead => export const isReadMethod = (method: Abi[number]): method is SmartContractMethodRead =>
method.type === 'function' && ( method.type === 'function' && (
method.constant || method.stateMutability === 'view' || method.stateMutability === 'pure' method.constant || method.stateMutability === 'view' || method.stateMutability === 'pure'
...@@ -26,13 +29,19 @@ export const isWriteMethod = (method: Abi[number]): method is SmartContractMetho ...@@ -26,13 +29,19 @@ export const isWriteMethod = (method: Abi[number]): method is SmartContractMetho
(method.type === 'function' || method.type === 'fallback' || method.type === 'receive') && (method.type === 'function' || method.type === 'fallback' || method.type === 'receive') &&
!isReadMethod(method); !isReadMethod(method);
const enrichWithMethodId = (method: AbiFunction): SmartContractMethodCustomFields => { export const enrichWithMethodId = (method: AbiFunction | AbiFallback | AbiReceive): SmartContractMethod => {
if (method.type !== 'function') {
return method;
}
try { try {
return { return {
...method,
method_id: toFunctionSelector(method).slice(2), method_id: toFunctionSelector(method).slice(2),
}; };
} catch (error) { } catch (error) {
return { return {
...method,
is_invalid: true, is_invalid: true,
}; };
} }
...@@ -42,22 +51,9 @@ export function divideAbiIntoMethodTypes(abi: Abi): DividedAbi { ...@@ -42,22 +51,9 @@ export function divideAbiIntoMethodTypes(abi: Abi): DividedAbi {
return { return {
read: abi read: abi
.filter(isReadMethod) .filter(isReadMethod)
.map((method) => ({ .map(enrichWithMethodId) as Array<SmartContractMethodRead>,
...method,
...enrichWithMethodId(method),
})),
write: abi write: abi
.filter(isWriteMethod) .filter(isWriteMethod)
.map((method) => { .map(enrichWithMethodId) as Array<SmartContractMethodWrite>,
if (method.type !== 'function') {
return method;
}
return {
...method,
...enrichWithMethodId(method),
};
}),
}; };
} }
...@@ -138,7 +138,11 @@ const AddressPageContent = () => { ...@@ -138,7 +138,11 @@ const AddressPageContent = () => {
const isSafeAddress = useIsSafeAddress(!addressQuery.isPlaceholderData && addressQuery.data?.is_contract ? hash : undefined); const isSafeAddress = useIsSafeAddress(!addressQuery.isPlaceholderData && addressQuery.data?.is_contract ? hash : undefined);
const safeIconColor = useColorModeValue('black', 'white'); const safeIconColor = useColorModeValue('black', 'white');
const contractTabs = useContractTabs(addressQuery.data, addressQuery.isPlaceholderData); const contractTabs = useContractTabs(
addressQuery.data,
config.features.mudFramework.isEnabled ? (mudTablesCountQuery.isPlaceholderData || addressQuery.isPlaceholderData) : addressQuery.isPlaceholderData,
Boolean(config.features.mudFramework.isEnabled && mudTablesCountQuery.data && mudTablesCountQuery.data > 0),
);
const tabs: Array<RoutedTab> = React.useMemo(() => { const tabs: Array<RoutedTab> = React.useMemo(() => {
return [ return [
......
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