diff --git a/lib/web3/client.ts b/lib/web3/client.ts index e41e345455..d89fcac808 100644 --- a/lib/web3/client.ts +++ b/lib/web3/client.ts @@ -2,10 +2,18 @@ import { createPublicClient, http } from 'viem'; import currentChain from './currentChain'; -export const publicClient = createPublicClient({ - chain: currentChain, - transport: http(), - batch: { - multicall: true, - }, -}); +export const publicClient = (() => { + if (currentChain.rpcUrls.public.http.filter(Boolean).length === 0) { + return; + } + + try { + return createPublicClient({ + chain: currentChain, + transport: http(), + batch: { + multicall: true, + }, + }); + } catch (error) {} +})(); diff --git a/ui/block/useBlockQuery.tsx b/ui/block/useBlockQuery.tsx index 7e4caf90db..5d60edc7b4 100644 --- a/ui/block/useBlockQuery.tsx +++ b/ui/block/useBlockQuery.tsx @@ -50,6 +50,10 @@ export default function useBlockQuery({ heightOrHash }: Params): BlockQuery { const rpcQuery = useQuery({ queryKey: [ 'RPC', 'block', { heightOrHash } ], queryFn: async() => { + if (!publicClient) { + return null; + } + const blockParams = heightOrHash.startsWith('0x') ? { blockHash: heightOrHash as `0x${ string }` } : { blockNumber: BigInt(heightOrHash) }; return publicClient.getBlock(blockParams).catch(() => null); }, @@ -86,13 +90,13 @@ export default function useBlockQuery({ heightOrHash }: Params): BlockQuery { }; }, placeholderData: GET_BLOCK, - enabled: apiQuery.isError || apiQuery.errorUpdateCount > 0, + enabled: publicClient !== undefined && (apiQuery.isError || apiQuery.errorUpdateCount > 0), retry: false, refetchOnMount: false, }); React.useEffect(() => { - if (apiQuery.isPlaceholderData) { + if (apiQuery.isPlaceholderData || !publicClient) { return; } @@ -109,7 +113,7 @@ export default function useBlockQuery({ heightOrHash }: Params): BlockQuery { } }, [ rpcQuery.data, rpcQuery.isPlaceholderData ]); - const isRpcQuery = Boolean((apiQuery.isError || apiQuery.isPlaceholderData) && apiQuery.errorUpdateCount > 0 && rpcQuery.data); + const isRpcQuery = Boolean(publicClient && (apiQuery.isError || apiQuery.isPlaceholderData) && apiQuery.errorUpdateCount > 0 && rpcQuery.data); const query = isRpcQuery ? rpcQuery as UseQueryResult> : apiQuery; return { diff --git a/ui/block/useBlockTxQuery.tsx b/ui/block/useBlockTxQuery.tsx index eafd168659..49a637b187 100644 --- a/ui/block/useBlockTxQuery.tsx +++ b/ui/block/useBlockTxQuery.tsx @@ -63,6 +63,10 @@ export default function useBlockTxQuery({ heightOrHash, blockQuery, tab }: Param const rpcQuery = useQuery({ queryKey: [ 'RPC', 'block_txs', { heightOrHash } ], queryFn: async() => { + if (!publicClient) { + return null; + } + const blockParams = heightOrHash.startsWith('0x') ? { blockHash: heightOrHash as `0x${ string }`, includeTransactions: true } : { blockNumber: BigInt(heightOrHash), includeTransactions: true }; @@ -125,13 +129,13 @@ export default function useBlockTxQuery({ heightOrHash, blockQuery, tab }: Param }; }, placeholderData: GET_BLOCK_WITH_TRANSACTIONS, - enabled: tab === 'txs' && (blockQuery.isDegradedData || apiQuery.isError || apiQuery.errorUpdateCount > 0), + enabled: publicClient !== undefined && tab === 'txs' && (blockQuery.isDegradedData || apiQuery.isError || apiQuery.errorUpdateCount > 0), retry: false, refetchOnMount: false, }); React.useEffect(() => { - if (apiQuery.isPlaceholderData) { + if (apiQuery.isPlaceholderData || !publicClient) { return; } @@ -151,7 +155,7 @@ export default function useBlockTxQuery({ heightOrHash, blockQuery, tab }: Param const isRpcQuery = Boolean(( blockQuery.isDegradedData || ((apiQuery.isError || apiQuery.isPlaceholderData) && apiQuery.errorUpdateCount > 0) - ) && rpcQuery.data); + ) && rpcQuery.data && publicClient); const rpcQueryWithPages: QueryWithPagesResult<'block_txs'> = React.useMemo(() => { return { diff --git a/ui/block/useBlockWithdrawalsQuery.tsx b/ui/block/useBlockWithdrawalsQuery.tsx index 670238793a..423032cf47 100644 --- a/ui/block/useBlockWithdrawalsQuery.tsx +++ b/ui/block/useBlockWithdrawalsQuery.tsx @@ -65,6 +65,10 @@ export default function useBlockWithdrawalsQuery({ heightOrHash, blockQuery, tab const rpcQuery = useQuery({ queryKey: [ 'RPC', 'block', { heightOrHash } ], queryFn: async() => { + if (!publicClient) { + return null; + } + const blockParams = heightOrHash.startsWith('0x') ? { blockHash: heightOrHash as `0x${ string }` } : { blockNumber: BigInt(heightOrHash) }; return publicClient.getBlock(blockParams).catch(() => null); }, @@ -89,6 +93,7 @@ export default function useBlockWithdrawalsQuery({ heightOrHash, blockQuery, tab }, placeholderData: GET_BLOCK, enabled: + publicClient !== undefined && tab === 'withdrawals' && config.features.beaconChain.isEnabled && (blockQuery.isDegradedData || apiQuery.isError || apiQuery.errorUpdateCount > 0), @@ -97,7 +102,7 @@ export default function useBlockWithdrawalsQuery({ heightOrHash, blockQuery, tab }); React.useEffect(() => { - if (apiQuery.isPlaceholderData) { + if (apiQuery.isPlaceholderData || !publicClient) { return; } @@ -117,7 +122,7 @@ export default function useBlockWithdrawalsQuery({ heightOrHash, blockQuery, tab const isRpcQuery = Boolean(( blockQuery.isDegradedData || ((apiQuery.isError || apiQuery.isPlaceholderData) && apiQuery.errorUpdateCount > 0) - ) && rpcQuery.data); + ) && rpcQuery.data && publicClient); const rpcQueryWithPages: QueryWithPagesResult<'block_withdrawals'> = React.useMemo(() => { return { diff --git a/ui/pages/Address.tsx b/ui/pages/Address.tsx index fdb5db012e..1b12c37401 100644 --- a/ui/pages/Address.tsx +++ b/ui/pages/Address.tsx @@ -67,7 +67,7 @@ const AddressPageContent = () => { const userOpsAccountQuery = useApiQuery('user_ops_account', { pathParams: { hash }, queryOptions: { - enabled: Boolean(hash), + enabled: Boolean(hash) && config.features.userOps.isEnabled, placeholderData: USER_OPS_ACCOUNT, }, }); @@ -160,16 +160,18 @@ const AddressPageContent = () => { ].filter(Boolean); }, [ addressQuery.data, contractTabs, addressTabsCountersQuery.data, userOpsAccountQuery.data ]); + const isLoading = addressQuery.isPlaceholderData || (config.features.userOps.isEnabled && userOpsAccountQuery.isPlaceholderData); + const tags = ( ); @@ -189,8 +191,6 @@ const AddressPageContent = () => { }; }, [ appProps.referrer ]); - const isLoading = addressQuery.isPlaceholderData; - const titleSecondRow = ( { addressQuery.data?.ens_domain_name && ( @@ -241,7 +241,7 @@ const AddressPageContent = () => { { /* should stay before tabs to scroll up with pagination */ } - { (addressQuery.isPlaceholderData || addressTabsCountersQuery.isPlaceholderData || userOpsAccountQuery.isPlaceholderData) ? + { (isLoading || addressTabsCountersQuery.isPlaceholderData) ? : content } diff --git a/ui/pages/Transaction.tsx b/ui/pages/Transaction.tsx index 3078a74e2b..37a3043227 100644 --- a/ui/pages/Transaction.tsx +++ b/ui/pages/Transaction.tsx @@ -7,6 +7,7 @@ import config from 'configs/app'; import { useAppContext } from 'lib/contexts/app'; import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError'; import getQueryParamString from 'lib/router/getQueryParamString'; +import { publicClient } from 'lib/web3/client'; import TextAd from 'ui/shared/ad/TextAd'; import EntityTags from 'ui/shared/EntityTags'; import PageTitle from 'ui/shared/Page/PageTitle'; @@ -33,7 +34,7 @@ const TransactionPageContent = () => { const txQuery = useTxQuery(); const { data, isPlaceholderData, isError, error, errorUpdateCount } = txQuery; - const showDegradedView = (isError || isPlaceholderData) && errorUpdateCount > 0; + const showDegradedView = publicClient && (isError || isPlaceholderData) && errorUpdateCount > 0; const tabs: Array = (() => { const detailsComponent = showDegradedView ? @@ -97,8 +98,10 @@ const TransactionPageContent = () => { return ; })(); - if (error?.status === 422) { - throwOnResourceLoadError({ resource: 'tx', error, isError: true }); + if (isError && !showDegradedView) { + if (error?.status === 422 || error?.status === 404) { + throwOnResourceLoadError({ resource: 'tx', error, isError: true }); + } } return ( diff --git a/ui/tx/TxDetails.tsx b/ui/tx/TxDetails.tsx index 61ee2196ea..f72012a234 100644 --- a/ui/tx/TxDetails.tsx +++ b/ui/tx/TxDetails.tsx @@ -1,6 +1,7 @@ import React from 'react'; import TestnetWarning from 'ui/shared/alerts/TestnetWarning'; +import DataFetchAlert from 'ui/shared/DataFetchAlert'; import TxInfo from './details/TxInfo'; import type { TxQuery } from './useTxQuery'; @@ -10,6 +11,10 @@ interface Props { } const TxDetails = ({ txQuery }: Props) => { + if (txQuery.isError) { + return ; + } + return ( <> diff --git a/ui/tx/TxDetailsDegraded.tsx b/ui/tx/TxDetailsDegraded.tsx index a486de3400..0793a9e46a 100644 --- a/ui/tx/TxDetailsDegraded.tsx +++ b/ui/tx/TxDetailsDegraded.tsx @@ -36,6 +36,10 @@ const TxDetailsDegraded = ({ hash, txQuery }: Props) => { const query = useQuery({ queryKey: [ 'RPC', 'tx', { hash } ], queryFn: async() => { + if (!publicClient) { + throw new Error('No public RPC client'); + } + const tx = await publicClient.getTransaction({ hash: hash as `0x${ string }` }); if (!tx) { diff --git a/ui/tx/TxUserOps.tsx b/ui/tx/TxUserOps.tsx index 8f313650b7..33cc899f7d 100644 --- a/ui/tx/TxUserOps.tsx +++ b/ui/tx/TxUserOps.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { USER_OPS_ITEM } from 'stubs/userOps'; import { generateListStub } from 'stubs/utils'; +import DataFetchAlert from 'ui/shared/DataFetchAlert'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import TxPendingAlert from 'ui/tx/TxPendingAlert'; import TxSocketAlert from 'ui/tx/TxSocketAlert'; @@ -28,6 +29,10 @@ const TxUserOps = ({ txQuery }: Props) => { return txQuery.socketStatus ? : ; } + if (txQuery.isError) { + return ; + } + return ; }; diff --git a/ui/userOps/UserOpsContent.tsx b/ui/userOps/UserOpsContent.tsx index 5416dc3a35..8d2b8c756c 100644 --- a/ui/userOps/UserOpsContent.tsx +++ b/ui/userOps/UserOpsContent.tsx @@ -26,7 +26,7 @@ const UserOpsContent = ({ query, showTx = true, showSender = true }: Props) => {