Files
zeekayandHanzo Dev 24d521e373 fix: three port leftovers the tokens page and footer never recovered from
Filter rendered twice on /tokens and dropped into the search input. Both
blocks in TokensActionBar are meant to exist — one for mobile with the sort
control, one for desktop inside the sticky ActionBar — and the Chakra port
turned each HStack into a bare div, losing display={{base,lg}} and gap. With
no breakpoint both drew at once, and with no flex the button, the sort and
the input stacked instead of sitting in a row. Every sibling action bar
(VerifiedContracts, NameDomains, Clusters) kept `flex lg:hidden` /
`hidden lg:flex`; tokens is the one file where the port dropped them, so this
restores that pattern rather than inventing a second one. Desktop now
measures one filter at x=32 w=92 and one input at x=136, no overlap.

The clipped label is a different fault underneath it. @luxfi/ui's ButtonFrame
sets inline-flex, alignItems, gap, a fixed height and overflow:hidden but
never flexDirection, and it is built on a react-native View, which lays out
in a column — so icon and label stacked and the fixed height cut the label
off. Swept 39 buttons over nine pages at both viewports: only three carry
more than one child and only Filter clips, so this says row at FilterButton,
the one component every filter in the app goes through. Inline because the
frame's own class outranks a utility. The primitive is still wrong and wants
fixing upstream.

The footer's social row was never a colour problem. Its branch of Footer
leaves the three left-hand blocks unwrapped, so a two-column grid takes them
as three children and the links land in the 470px track instead of the wide
one; five 160px columns do not fit there, the two mobile 1fr columns were
still the only explicit tracks at lg, and they collapsed to zero width with
Contribute, X (Twitter) and Discord painting on top of each other. Wrapping
the left column the way the custom-links branch already does, and dropping
the mobile tracks at lg, spreads them 576 to 1392 with nothing overlapping at
1440, 1024 or 390.

Addresses render check-summed again. The comment said the API always answers
in EIP-55; it case-folds now, deliberately, so a checksummed URL resolves
instead of 404ing, and `data.hash ?? getCheckedSummedAddress(hash)` took the
lower-case payload every time the address existed. Checksum whatever is
displayed instead — getAddress reads any case and is idempotent, so one call
covers a hash from the API, from the route, or from neither yet. Display
only; the backend keeps folding.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 21:53:27 -07:00

494 lines
22 KiB
TypeScript

import { useRouter } from 'next/router';
import React from 'react';
import type { TabItemRegular } from 'toolkit/components/AdaptiveTabs/types';
import type { EntityTag } from 'ui/shared/EntityTags/types';
import config from 'configs/app';
import getCheckedSummedAddress from 'lib/address/getCheckedSummedAddress';
import useAddressMetadataInfoQuery from 'lib/address/useAddressMetadataInfoQuery';
import useAddressMetadataInitUpdate from 'lib/address/useAddressMetadataInitUpdate';
import useApiQuery from 'lib/api/useApiQuery';
import { useAddressClusters } from 'lib/clusters/useAddressClusters';
import useAddressProfileApiQuery from 'lib/hooks/useAddressProfileApiQuery';
import useIsSafeAddress from 'lib/hooks/useIsSafeAddress';
import getNetworkValidationActionText from 'lib/networks/getNetworkValidationActionText';
import getQueryParamString from 'lib/router/getQueryParamString';
import useEtherscanRedirects from 'lib/router/useEtherscanRedirects';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import useFetchXStarScore from 'lib/xStarScore/useFetchXStarScore';
import { ADDRESS_TABS_COUNTERS } from 'stubs/address';
import { USER_OPS_ACCOUNT } from 'stubs/userOps';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import Address3rdPartyWidgets from 'ui/address/Address3rdPartyWidgets';
import useAddress3rdPartyWidgets from 'ui/address/address3rdPartyWidgets/useAddress3rdPartyWidgets';
import AddressAccountHistory from 'ui/address/AddressAccountHistory';
import AddressBlocksValidated from 'ui/address/AddressBlocksValidated';
import AddressCoinBalance from 'ui/address/AddressCoinBalance';
import AddressContract from 'ui/address/AddressContract';
import AddressDeposits from 'ui/address/AddressDeposits';
import AddressDetails from 'ui/address/AddressDetails';
import AddressEpochRewards from 'ui/address/AddressEpochRewards';
import AddressInternalTxs from 'ui/address/AddressInternalTxs';
import AddressLogs from 'ui/address/AddressLogs';
import AddressMud from 'ui/address/AddressMud';
import AddressMultichainInfoButton from 'ui/address/AddressMultichainInfoButton';
import AddressTokens from 'ui/address/AddressTokens';
import AddressTokenTransfers, { ADDRESS_TOKEN_TRANSFERS_TAB_IDS } from 'ui/address/AddressTokenTransfers';
import AddressTxs, { ADDRESS_TXS_TAB_IDS } from 'ui/address/AddressTxs';
import AddressUserOps from 'ui/address/AddressUserOps';
import AddressWithdrawals from 'ui/address/AddressWithdrawals';
import AddressClusters from 'ui/address/clusters/AddressClusters';
import { CONTRACT_TAB_IDS } from 'ui/address/contract/utils';
import AddressAlerts from 'ui/address/details/AddressAlerts';
import AddressFavoriteButton from 'ui/address/details/AddressFavoriteButton';
import AddressQrCode from 'ui/address/details/AddressQrCode';
import AddressEnsDomains from 'ui/address/ensDomains/AddressEnsDomains';
import SolidityscanReport from 'ui/address/SolidityscanReport';
import useAddressCountersQuery from 'ui/address/utils/useAddressCountersQuery';
import useAddressQuery from 'ui/address/utils/useAddressQuery';
import useCheckAddressFormat from 'ui/address/utils/useCheckAddressFormat';
import useCheckDomainNameParam from 'ui/address/utils/useCheckDomainNameParam';
import AccountActionsMenu from 'ui/shared/AccountActionsMenu/AccountActionsMenu';
import TextAd from 'ui/shared/ad/TextAd';
import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import EnsEntity from 'ui/shared/entities/ens/EnsEntity';
import EntityTags from 'ui/shared/EntityTags/EntityTags';
import formatUserTags from 'ui/shared/EntityTags/formatUserTags';
import sortEntityTags from 'ui/shared/EntityTags/sortEntityTags';
import IconSvg from 'ui/shared/IconSvg';
import NetworkExplorers from 'ui/shared/NetworkExplorers';
import PageTitle from 'ui/shared/Page/PageTitle';
const TOKEN_TABS = [ 'tokens_erc20', 'tokens_nfts', 'tokens_nfts_collection', 'tokens_nfts_list' ];
const PREDEFINED_TAG_PRIORITY = 100;
const FHE_TOOLTIP_DESCRIPTION = 'This contract uses Fully Homomorphic Encryption (FHE) to encrypt on-chain data. ' +
'Inputs and most outputs are intentionally hidden, while computations are verified on-chain.';
const txInterpretation = config.features.txInterpretation;
const addressProfileAPIFeature = config.features.addressProfileAPI;
const xScoreFeature = config.features.xStarScore;
const nameServicesFeature = config.features.nameServices;
const beaconChainFeature = config.features.beaconChain;
const AddressPageContent = () => {
const router = useRouter();
const hash = getQueryParamString(router.query.hash);
const checkDomainName = useCheckDomainNameParam(hash);
const checkAddressFormat = useCheckAddressFormat(hash);
useEtherscanRedirects();
const areQueriesEnabled = !checkDomainName && !checkAddressFormat;
const addressQuery = useAddressQuery({ hash, isEnabled: areQueriesEnabled });
const addressTabsCountersQuery = useApiQuery('general:address_tabs_counters', {
pathParams: { hash },
queryOptions: {
enabled: areQueriesEnabled && Boolean(hash),
placeholderData: ADDRESS_TABS_COUNTERS,
},
});
const countersQuery = useAddressCountersQuery({
hash,
isLoading: addressQuery.isPlaceholderData,
isDegradedData: addressQuery.isDegradedData,
});
const userOpsAccountQuery = useApiQuery('general:user_ops_account', {
pathParams: { hash },
queryOptions: {
enabled: areQueriesEnabled && Boolean(hash) && config.features.userOps.isEnabled,
placeholderData: USER_OPS_ACCOUNT,
},
});
const mudTablesCountQuery = useApiQuery('general:mud_tables_count', {
pathParams: { hash },
queryOptions: {
enabled: config.features.mudFramework.isEnabled && areQueriesEnabled && Boolean(hash),
placeholderData: 10,
},
});
const addressesForMetadataQuery = React.useMemo(() => ([ hash ].filter(Boolean)), [ hash ]);
const addressMetadataQuery = useAddressMetadataInfoQuery(addressesForMetadataQuery, areQueriesEnabled);
const userPropfileApiQuery = useAddressProfileApiQuery(hash, addressProfileAPIFeature.isEnabled && areQueriesEnabled);
const addressEnsDomainsQuery = useApiQuery('bens:addresses_lookup', {
queryParams: {
address: hash,
protocols: nameServicesFeature.isEnabled && nameServicesFeature.ens.isEnabled ? nameServicesFeature.ens.protocols : undefined,
resolved_to: true,
owned_by: true,
only_active: true,
order: 'ASC',
},
queryOptions: {
enabled: Boolean(hash) && nameServicesFeature.isEnabled && nameServicesFeature.ens.isEnabled,
},
});
const addressMainDomain = !addressQuery.isPlaceholderData ?
addressEnsDomainsQuery.data?.items.find((domain) => domain.name === addressQuery.data?.ens_domain_name) :
undefined;
const addressClustersQuery = useAddressClusters(hash, areQueriesEnabled);
const addressType = addressQuery.data?.is_contract && addressQuery.data?.proxy_type !== 'eip7702' ? 'contract' : 'eoa';
const address3rdPartyWidgets = useAddress3rdPartyWidgets(addressType, addressQuery.isPlaceholderData, areQueriesEnabled);
const isLoading = addressQuery.isPlaceholderData;
const isTabsLoading =
isLoading ||
addressTabsCountersQuery.isPlaceholderData ||
(address3rdPartyWidgets.isEnabled && address3rdPartyWidgets.configQuery.isPlaceholderData) ||
(config.features.userOps.isEnabled && userOpsAccountQuery.isPlaceholderData) ||
(config.features.mudFramework.isEnabled && mudTablesCountQuery.isPlaceholderData);
const handleFetchedBytecodeMessage = React.useCallback(() => {
addressQuery.refetch();
}, [ addressQuery ]);
const channel = useSocketChannel({
topic: `addresses:${ hash?.toLowerCase() }`,
isDisabled: isTabsLoading || addressQuery.isDegradedData || Boolean(addressQuery.data?.is_contract),
});
useSocketMessage({
channel,
event: 'fetched_bytecode',
handler: handleFetchedBytecodeMessage,
});
useAddressMetadataInitUpdate({
address: hash,
counters: countersQuery.data,
isEnabled: !countersQuery.isPlaceholderData && !countersQuery.isDegradedData,
});
const isSafeAddress = useIsSafeAddress(!addressQuery.isPlaceholderData && addressQuery.data?.is_contract ? addressQuery.data.hash : undefined);
const xStarQuery = useFetchXStarScore({ hash });
const tabs: Array<TabItemRegular> = React.useMemo(() => {
return [
{
id: 'index',
title: 'Details',
component: <AddressDetails addressQuery={ addressQuery } countersQuery={ countersQuery } isLoading={ isTabsLoading }/>,
},
addressQuery.data?.is_contract ? {
id: 'contract',
title: () => {
const tabName = addressQuery.data.proxy_type === 'eip7702' ? 'Code' : 'Contract';
if (addressQuery.data.is_verified) {
return (
<>
<span>{ tabName }</span>
<IconSvg name="status/success" className="size-[14px] text-[var(--color-green-500)]"/>
</>
);
}
return tabName;
},
component: (
<AddressContract
addressData={ addressQuery.data }
isLoading={ isTabsLoading }
hasMudTab={ Boolean(config.features.mudFramework.isEnabled && mudTablesCountQuery.data && mudTablesCountQuery.data > 0) }
/>
),
subTabs: CONTRACT_TAB_IDS,
} : undefined,
config.features.mudFramework.isEnabled && mudTablesCountQuery.data && mudTablesCountQuery.data > 0 && {
id: 'mud',
title: 'MUD',
count: mudTablesCountQuery.data,
component: <AddressMud shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
},
{
id: 'txs',
title: 'Transactions',
count: addressTabsCountersQuery.data?.transactions_count,
component: <AddressTxs shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
subTabs: ADDRESS_TXS_TAB_IDS,
},
txInterpretation.isEnabled && txInterpretation.provider === 'noves' ?
{
id: 'account_history',
title: 'Account history',
component: <AddressAccountHistory shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
} :
undefined,
config.features.userOps.isEnabled && Boolean(userOpsAccountQuery.data?.total_ops) ?
{
id: 'user_ops',
title: 'User operations',
count: userOpsAccountQuery.data?.total_ops,
component: <AddressUserOps shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
} :
undefined,
beaconChainFeature.isEnabled && !beaconChainFeature.withdrawalsOnly && addressTabsCountersQuery.data?.beacon_deposits_count ?
{
id: 'deposits',
title: 'Deposits',
count: addressTabsCountersQuery.data?.beacon_deposits_count,
component: <AddressDeposits shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
} :
undefined,
config.features.beaconChain.isEnabled && addressTabsCountersQuery.data?.withdrawals_count ?
{
id: 'withdrawals',
title: 'Withdrawals',
count: addressTabsCountersQuery.data?.withdrawals_count,
component: <AddressWithdrawals shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
} :
undefined,
{
id: 'token_transfers',
title: 'Token transfers',
count: addressTabsCountersQuery.data?.token_transfers_count,
component: <AddressTokenTransfers shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
subTabs: ADDRESS_TOKEN_TRANSFERS_TAB_IDS,
},
{
id: 'tokens',
title: 'Tokens',
count: addressTabsCountersQuery.data?.token_balances_count,
component: <AddressTokens shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
subTabs: TOKEN_TABS,
},
{
id: 'internal_txns',
title: 'Internal txns',
count: addressTabsCountersQuery.data?.internal_transactions_count,
component: <AddressInternalTxs shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
},
addressTabsCountersQuery.data?.celo_election_rewards_count ? {
id: 'epoch_rewards',
title: 'Epoch rewards',
count: addressTabsCountersQuery.data?.celo_election_rewards_count,
component: <AddressEpochRewards shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
} : undefined,
{
id: 'coin_balance_history',
title: 'Coin balance history',
component: <AddressCoinBalance shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
},
addressTabsCountersQuery.data?.validations_count ?
{
id: 'blocks_validated',
title: `Blocks ${ getNetworkValidationActionText() }`,
count: addressTabsCountersQuery.data?.validations_count,
component: <AddressBlocksValidated shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
} :
undefined,
addressTabsCountersQuery.data?.logs_count ?
{
id: 'logs',
title: 'Logs',
count: addressTabsCountersQuery.data?.logs_count,
component: <AddressLogs shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
} :
undefined,
(address3rdPartyWidgets.isEnabled && address3rdPartyWidgets.items.length > 0) ? {
id: 'widgets',
title: 'Widgets',
count: address3rdPartyWidgets.items.length,
component: (
<Address3rdPartyWidgets
addressType={ addressType }
isLoading={ addressQuery.isPlaceholderData }
shouldRender={ !isTabsLoading }
isQueryEnabled={ areQueriesEnabled }
showAll
/>
),
} : undefined,
].filter(Boolean);
}, [
addressQuery,
countersQuery,
addressTabsCountersQuery.data,
userOpsAccountQuery.data,
isTabsLoading,
areQueriesEnabled,
mudTablesCountQuery.data,
address3rdPartyWidgets,
addressType,
]);
const usernameApiTag = userPropfileApiQuery.data?.user_profile?.username;
const tags: Array<EntityTag> = React.useMemo(() => {
return [
...(addressQuery.data?.public_tags?.map((tag) => {
const isFhe = tag.label.toLowerCase() === 'fhe' || tag.display_name.toLowerCase() === 'fhe';
return {
slug: tag.label,
name: tag.display_name,
tagType: 'custom' as const,
ordinal: -1,
meta: isFhe ? {
tooltipDescription: FHE_TOOLTIP_DESCRIPTION,
} : undefined,
};
}) || []),
addressQuery.data?.celo?.account ? {
slug: 'celo-account',
name: 'Celo account',
tagType: 'custom' as const,
ordinal: PREDEFINED_TAG_PRIORITY,
meta: {
bgColor: 'yellow.200',
textColor: 'black',
},
} : undefined,
!addressQuery.data?.is_contract ? { slug: 'eoa', name: 'EOA', tagType: 'custom' as const, ordinal: PREDEFINED_TAG_PRIORITY } : undefined,
config.features.validators.isEnabled && addressQuery.data?.has_validated_blocks ?
{ slug: 'validator', name: 'Validator', tagType: 'custom' as const, ordinal: PREDEFINED_TAG_PRIORITY } :
undefined,
addressQuery.data?.implementations?.length && addressQuery.data?.proxy_type !== 'eip7702' ?
{ slug: 'proxy', name: 'Proxy', tagType: 'custom' as const, ordinal: PREDEFINED_TAG_PRIORITY } :
undefined,
addressQuery.data?.implementations?.length && addressQuery.data?.proxy_type === 'eip7702' ?
{ slug: 'eip7702', name: 'EOA+code', tagType: 'custom' as const, ordinal: PREDEFINED_TAG_PRIORITY } :
undefined,
addressQuery.data?.token ? { slug: 'token', name: 'Token', tagType: 'custom' as const, ordinal: PREDEFINED_TAG_PRIORITY } : undefined,
isSafeAddress ? { slug: 'safe', name: 'Multisig: Safe', tagType: 'custom' as const, ordinal: -10 } : undefined,
addressProfileAPIFeature.isEnabled && usernameApiTag ? {
slug: 'username_api',
name: usernameApiTag,
tagType: 'custom' as const,
ordinal: 11,
meta: {
tagIcon: addressProfileAPIFeature.tagIcon,
bgColor: addressProfileAPIFeature.tagBgColor,
textColor: addressProfileAPIFeature.tagTextColor,
tagUrl: addressProfileAPIFeature.tagLinkTemplate ? addressProfileAPIFeature.tagLinkTemplate.replace('{username}', usernameApiTag) : undefined,
},
} : undefined,
config.features.userOps.isEnabled && userOpsAccountQuery.data ?
{ slug: 'user_ops_acc', name: 'Smart contract wallet', tagType: 'custom' as const, ordinal: PREDEFINED_TAG_PRIORITY } :
undefined,
config.features.mudFramework.isEnabled && mudTablesCountQuery.data ?
{ slug: 'mud', name: 'MUD World', tagType: 'custom' as const, ordinal: PREDEFINED_TAG_PRIORITY } :
undefined,
...formatUserTags(addressQuery.data),
...(addressMetadataQuery.data?.addresses?.[hash.toLowerCase()]?.tags.filter(tag => tag.tagType !== 'note') || []),
!addressQuery.data?.is_contract && xScoreFeature.isEnabled && xStarQuery.data?.data.level ?
{
slug: 'xstar',
name: `XHS ${ xStarQuery.data.data.level } level`,
tagType: 'custom' as const,
ordinal: 12,
meta: {
tooltipTitle: 'XStar humanity levels',
tooltipDescription:
'XStar looks for off-chain information about an address and interpret it as a XHS score. Different score means different humanity levels.',
tooltipUrl: xScoreFeature.url,
},
} :
undefined,
].filter(Boolean).sort(sortEntityTags);
}, [
addressMetadataQuery.data,
addressQuery.data,
hash,
isSafeAddress,
userOpsAccountQuery.data,
mudTablesCountQuery.data,
usernameApiTag,
xStarQuery.data?.data,
]);
const titleContentAfter = (
<EntityTags
tags={ tags }
addressHash={ addressQuery.data?.hash }
isLoading={
isLoading ||
(config.features.userOps.isEnabled && userOpsAccountQuery.isPlaceholderData) ||
(config.features.addressMetadata.isEnabled && addressMetadataQuery.isPending) ||
(addressProfileAPIFeature.isEnabled && userPropfileApiQuery.isPending) ||
(xScoreFeature.isEnabled && xStarQuery.isPlaceholderData)
}
/>
);
// The API case-folds the hash it echoes back, so checksum whatever we display
// rather than trusting the payload to arrive check-summed. getAddress reads
// any case and is idempotent, so this is the same one call for a hash that
// came from the API, from the route, or from neither yet.
const checkSummedHash = React.useMemo(() => {
return getCheckedSummedAddress(isLoading ? hash : addressQuery.data?.hash ?? hash);
}, [ hash, addressQuery.data?.hash, isLoading ]);
const titleSecondRow = (
<div className="flex items-center w-full gap-x-2 gap-y-2 flex-wrap lg:flex-nowrap">
{ addressQuery.data?.ens_domain_name && (
<EnsEntity
domain={ addressQuery.data?.ens_domain_name }
protocol={ !addressEnsDomainsQuery.isPending ? addressMainDomain?.protocol : null }
variant="subheading"
className="mr-1 max-w-[300px]"
/>
) }
<AddressEntity
address={{
...addressQuery.data,
hash: checkSummedHash,
name: '',
ens_domain_name: '',
implementations: null,
}}
isLoading={ isLoading }
variant="subheading"
noLink
isSafeAddress={ isSafeAddress }
icon={{ color: isSafeAddress ? 'var(--color-text-primary)' : undefined }}
/>
{ !isLoading && addressQuery.data?.is_contract && addressQuery.data.token &&
<AddressAddToWallet token={ addressQuery.data.token } variant="button"/> }
{ !isLoading && !addressQuery.data?.is_contract && config.features.account.isEnabled && (
<AddressFavoriteButton hash={ hash } watchListId={ addressQuery.data?.watchlist_address_id }/>
) }
<AddressQrCode hash={ addressQuery.data?.filecoin?.robust ?? checkSummedHash } isLoading={ isLoading }/>
<AccountActionsMenu isLoading={ isLoading }/>
<div className="flex gap-2 ml-auto"/>
<AddressMultichainInfoButton loading={ isLoading } addressData={ addressQuery.data }/>
{ !isLoading && addressQuery.data?.is_contract && addressQuery.data?.is_verified && config.UI.views.address.solidityscanEnabled &&
<SolidityscanReport hash={ hash }/> }
{ !isLoading && nameServicesFeature.isEnabled && nameServicesFeature.ens.isEnabled &&
<AddressEnsDomains query={ addressEnsDomainsQuery } addressHash={ hash } mainDomainName={ addressQuery.data?.ens_domain_name }/> }
{ !isLoading && nameServicesFeature.isEnabled && nameServicesFeature.clusters.isEnabled &&
<AddressClusters query={ addressClustersQuery } addressHash={ hash }/> }
<NetworkExplorers type="address" pathParam={ hash }/>
</div>
);
return (
<>
<TextAd className="mb-6"/>
<PageTitle
title={ `${ addressQuery.data?.is_contract && addressQuery.data?.proxy_type !== 'eip7702' ? 'Contract' : 'Address' } details` }
contentAfter={ titleContentAfter }
secondRow={ titleSecondRow }
isLoading={ isLoading }
/>
{ !addressMetadataQuery.isPending &&
<AddressAlerts tags={ addressMetadataQuery.data?.addresses?.[hash.toLowerCase()]?.tags }/> }
{ config.features.metasuites.isEnabled && <div className="hidden" id="meta-suites__address" data-ready={ !isLoading }/> }
<RoutedTabs tabs={ tabs } isLoading={ isTabsLoading }/>
</>
);
};
export default AddressPageContent;