diff --git a/src/components/Address/AccountAddressFormatted.tsx b/src/components/Address/AccountAddressFormatted.tsx index 9bf76a8..bc9023a 100644 --- a/src/components/Address/AccountAddressFormatted.tsx +++ b/src/components/Address/AccountAddressFormatted.tsx @@ -1,6 +1,7 @@ import { ethers } from 'ethers' import { FC } from 'react' +import { useAddressDisplay } from '../../hooks/useAddressDisplay' import { useAppSelector } from '../../redux/hooks' import { Network } from '../../redux/networks' import { @@ -18,31 +19,36 @@ export const AccountAddressFormatted: FC<{ const addressBookEntry = useAppSelector((state) => addressBookSelectors.selectById(state, createEntryId(network, address)) ) + const addressDisplay = useAddressDisplay(address) + const parsedAddress = ellipsis ? ellipsisAddress(ethers.utils.getAddress(address), ellipsis) : ethers.utils.getAddress(address) + // Priority: Address Book > Whois recommended name > shortened address + const displayName = addressBookEntry?.nameTag || addressDisplay.recommendedName || null + if (format === 'addressPlusName') { return ( <> {parsedAddress} - {!!addressBookEntry?.nameTag && ` (${addressBookEntry.nameTag})`} + {!!displayName && ` (${displayName})`} ) } if (format === 'namePlusAddress') { - if (!addressBookEntry?.nameTag) { + if (!displayName) { return <>{parsedAddress} } else { return ( <> - {addressBookEntry.nameTag} ({parsedAddress}) + {displayName} ({parsedAddress}) ) } } // "nameOnly" is default - return <>{addressBookEntry?.nameTag || parsedAddress} + return <>{displayName || parsedAddress} } diff --git a/src/components/Address/AddressDisplay.tsx b/src/components/Address/AddressDisplay.tsx index 1645a20..521e4f3 100644 --- a/src/components/Address/AddressDisplay.tsx +++ b/src/components/Address/AddressDisplay.tsx @@ -13,7 +13,10 @@ export default memo(function AddressDisplay({ length = 'short' }: AddressDisplayProps) { const addressDisplay = useAddressDisplay(address) - if (addressDisplay.ensName) { + + if (addressDisplay.recommendedName) { + return <>{addressDisplay.recommendedName} + } else if (addressDisplay.ensName) { return <>{addressDisplay.ensName} } else { return ( diff --git a/src/components/AddressBook/AddressBook.tsx b/src/components/AddressBook/AddressBook.tsx index 24f9ee8..ab47266 100644 --- a/src/components/AddressBook/AddressBook.tsx +++ b/src/components/AddressBook/AddressBook.tsx @@ -3,6 +3,7 @@ import StarBorderIcon from '@mui/icons-material/StarBorder' import { Avatar, IconButton, SvgIconProps, Tooltip } from '@mui/material' import { FC, useState } from 'react' +import { useAddressDisplay } from '../../hooks/useAddressDisplay' import { useAppSelector } from '../../redux/hooks' import { Network } from '../../redux/networks' import { @@ -21,9 +22,10 @@ export const AddressBookButton: FC<{ addressBookSelectors.selectById(state, createEntryId(network, address)) ) const [isDialogOpen, setIsDialogOpen] = useState(false) + const addressDisplay = useAddressDisplay(address) - const avatarUrl = ensApi.useLookupAvatarQuery(address) - + const avatarUrl = addressDisplay.recommendedAvatar + return ( <> setIsDialogOpen(false)} /> - {avatarUrl.currentData ? ( - - ) : ( - '' + {avatarUrl && ( + )} ) diff --git a/src/hooks/useAddressDisplay.tsx b/src/hooks/useAddressDisplay.tsx index 402b321..1e7c560 100644 --- a/src/hooks/useAddressDisplay.tsx +++ b/src/hooks/useAddressDisplay.tsx @@ -2,11 +2,16 @@ import { ethers } from 'ethers' import { useMemo } from 'react' import { ensApi } from '../redux/slices/ensResolver.slice' +import { whoisApi } from '../redux/slices/whoisResolver.slice' interface AddressDisplayResult { addressChecksummed: string | null | undefined ensName: string | null | undefined avatar: string | null | undefined + whoisName: string | null | undefined + whoisAvatar: string | null | undefined + recommendedName: string | null | undefined + recommendedAvatar: string | null | undefined isFetching: boolean } @@ -32,11 +37,23 @@ export const useName = (name: string, skip: boolean): AddressDisplayResult => { const ensAddressQuery = ensApi.useResolveNameQuery(name, { skip }) + + const whoisQuery = whoisApi.useResolveAddressQuery( + ensAddressQuery.currentData?.address || 'skip', + { + skip: !ensAddressQuery.currentData?.address + } + ) + return { addressChecksummed: ensAddressQuery.currentData?.address, ensName: !!ensAddressQuery.currentData?.address ? name : null, avatar: undefined, - isFetching: ensAddressQuery.isFetching + whoisName: whoisQuery.data?.recommendedName, + whoisAvatar: whoisQuery.data?.recommendedAvatar, + recommendedName: whoisQuery.data?.recommendedName || (!!ensAddressQuery.currentData?.address ? name : null), + recommendedAvatar: whoisQuery.data?.recommendedAvatar || whoisQuery.data?.ENS?.avatarUrl, + isFetching: ensAddressQuery.isFetching || whoisQuery.isFetching } } @@ -44,15 +61,28 @@ export const useAddress = ( address: string, skip: boolean ): AddressDisplayResult => { - const ensLookupQuery = ensApi.useLookupAddressQuery(address, { + const whoisQuery = whoisApi.useResolveAddressQuery(address, { skip }) + + const needsEnsLookup = !skip && !whoisQuery.data?.ENS?.handle && !whoisQuery.isFetching + const ensLookupQuery = ensApi.useLookupAddressQuery(address, { + skip: skip || !needsEnsLookup + }) + + //Use fallback from ENS if whois is not returning the name + const ensName = whoisQuery.data?.ENS?.handle || ensLookupQuery.data?.name + return { addressChecksummed: !skip ? ethers.utils.getAddress(address.toLowerCase()) : undefined, - ensName: ensLookupQuery.data?.name, + ensName: ensName, avatar: undefined, - isFetching: ensLookupQuery.isFetching + whoisName: whoisQuery.data?.recommendedName, + whoisAvatar: whoisQuery.data?.recommendedAvatar, + recommendedName: whoisQuery.data?.recommendedName || ensName, + recommendedAvatar: whoisQuery.data?.recommendedAvatar || whoisQuery.data?.ENS?.avatarUrl, + isFetching: whoisQuery.isFetching || ensLookupQuery.isFetching } } diff --git a/src/hooks/useSearch.ts b/src/hooks/useSearch.ts index ec614d6..ed5f9a2 100644 --- a/src/hooks/useSearch.ts +++ b/src/hooks/useSearch.ts @@ -172,7 +172,7 @@ export const useSearch = (searchTerm: string) => { .map((x) => ({ ...x, id: ethers.utils.getAddress(x.id), - ENS: addressDisplay.ensName + ENS: addressDisplay.recommendedName || addressDisplay.ensName })), (x) => x.id ), diff --git a/src/pages/[_network]/accounts/[_id].page.tsx b/src/pages/[_network]/accounts/[_id].page.tsx index 4e2f429..4236e60 100644 --- a/src/pages/[_network]/accounts/[_id].page.tsx +++ b/src/pages/[_network]/accounts/[_id].page.tsx @@ -37,12 +37,12 @@ import EventTableWithInfo from '../../../components/Table/EventTableWithInfo' import TokenChip from '../../../components/TokenChip/TokenChip' import IdContext from '../../../contexts/IdContext' import { useNetworkContext } from '../../../contexts/NetworkContext' +import { useAddressDisplay } from '../../../hooks/useAddressDisplay' import { useAppSelector } from '../../../redux/hooks' import { addressBookSelectors, createEntryId } from '../../../redux/slices/addressBook.slice' -import { ensApi } from '../../../redux/slices/ensResolver.slice' import { sfSubgraph } from '../../../redux/store' import ellipsisAddress from '../../../utils/ellipsisAddress' import SubgraphQueryLink from '../../subgraph/SubgraphQueryLink' @@ -77,9 +77,7 @@ const AccountPage: NextPage = () => { id: address }) - const ensAddressQuery = ensApi.useLookupAddressQuery(address) - - const ensName = ensAddressQuery.currentData?.name + const addressDisplay = useAddressDisplay(address) const prefetchStreamsQuery = sfSubgraph.usePrefetch('streams') const prefetchIndexesQuery = sfSubgraph.usePrefetch('indexes') @@ -164,8 +162,8 @@ const AccountPage: NextPage = () => { component="h1" sx={{ mx: 1 }} > - {/* TODO(KK): When there's an address book entry then ENS name is not displayed anywhere. Not sure if I like it... */} - {addressBookEntry ? addressBookEntry.nameTag : ensName} + {/* Priority: Address Book > Whois recommended name */} + {addressBookEntry ? addressBookEntry.nameTag : addressDisplay.recommendedName} ({ + resolveAddress: builder.query({ + queryFn: async (address) => { + try { + const response = await fetch( + `https://whois.superfluid.finance/api/resolve/${address}` + ) + + if (!response.ok) { + return { data: null } + } + + const data = await response.json() + return { data } + } catch (error) { + console.error('Whois API error:', error) + return { data: null } + } + } + }) + }) +}) \ No newline at end of file diff --git a/src/redux/store.ts b/src/redux/store.ts index a312fd5..f3e2af3 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -36,6 +36,7 @@ import { networks } from './networks' import { addressBookSlice } from './slices/addressBook.slice' import { themePreferenceSlice } from './slices/appPreferences.slice' import { ensApi } from './slices/ensResolver.slice' +import { whoisApi } from './slices/whoisResolver.slice' export const rpcApi = initializeRpcApiSlice(createApiWithReactHooks) .injectEndpoints(balanceRpcApiEndpoints) @@ -81,7 +82,8 @@ export const makeStore = wrapMakeStore(() => { [sfSubgraph.reducerPath]: sfSubgraph.reducer, [themePreferenceSlice.name]: themePreferenceSlice.reducer, [addressBookSlice.name]: addressBookReducer, - [ensApi.reducerPath]: ensApi.reducer + [ensApi.reducerPath]: ensApi.reducer, + [whoisApi.reducerPath]: whoisApi.reducer }, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ @@ -107,6 +109,7 @@ export const makeStore = wrapMakeStore(() => { .concat(rpcApi.middleware) .concat(sfSubgraph.middleware) .concat(ensApi.middleware) + .concat(whoisApi.middleware) }) if (!isServer()) {