-
Notifications
You must be signed in to change notification settings - Fork 276
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
317 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { getAvatars } from '@/identity/utils/getAvatars'; | ||
import { DEFAULT_QUERY_OPTIONS } from '@/internal/constants'; | ||
import { useQuery } from '@tanstack/react-query'; | ||
import { mainnet } from 'viem/chains'; | ||
import type { GetAvatarReturnType, UseQueryOptions } from '../types'; | ||
|
||
/** | ||
* Interface for the useAvatars hook options | ||
*/ | ||
export type UseAvatarsOptions = { | ||
ensNames: string[]; | ||
chain?: typeof mainnet; | ||
} | ||
|
||
/** | ||
* A React hook that leverages the `@tanstack/react-query` for fetching and optionally caching | ||
* multiple Basenames or ENS avatars in a single batch request. | ||
*/ | ||
export const useAvatars = ( | ||
{ ensNames, chain = mainnet }: UseAvatarsOptions, | ||
queryOptions?: UseQueryOptions, | ||
) => { | ||
const { enabled, cacheTime, staleTime, refetchOnWindowFocus } = { | ||
...DEFAULT_QUERY_OPTIONS, | ||
...queryOptions, | ||
}; | ||
|
||
// Create a stable query key that includes all ENS names | ||
const namesKey = ensNames.join(','); | ||
const queryKey = ['useAvatars', namesKey, chain.id]; | ||
|
||
return useQuery<GetAvatarReturnType[]>({ | ||
queryKey, | ||
queryFn: () => getAvatars({ ensNames, chain }), | ||
gcTime: cacheTime, | ||
staleTime, | ||
enabled: enabled && ensNames.length > 0, | ||
refetchOnWindowFocus, | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { getNames } from '@/identity/utils/getNames'; | ||
import { DEFAULT_QUERY_OPTIONS } from '@/internal/constants'; | ||
import { useQuery } from '@tanstack/react-query'; | ||
import { mainnet } from 'viem/chains'; | ||
import type { Address } from 'viem'; | ||
import type { GetNameReturnType, UseQueryOptions } from '../types'; | ||
|
||
/** | ||
* Interface for the useNames hook options | ||
*/ | ||
export type UseNamesOptions = { | ||
addresses: Address[]; | ||
chain?: typeof mainnet; | ||
} | ||
|
||
/** | ||
* A React hook that leverages the `@tanstack/react-query` for fetching and optionally caching | ||
* multiple Basenames or ENS names in a single batch request. | ||
*/ | ||
export const useNames = ( | ||
{ addresses, chain = mainnet }: UseNamesOptions, | ||
queryOptions?: UseQueryOptions, | ||
) => { | ||
const { enabled, cacheTime, staleTime, refetchOnWindowFocus } = { | ||
...DEFAULT_QUERY_OPTIONS, | ||
...queryOptions, | ||
}; | ||
|
||
// Create a stable query key that includes all addresses | ||
const addressesKey = addresses.join(','); | ||
const queryKey = ['useNames', addressesKey, chain.id]; | ||
|
||
return useQuery<GetNameReturnType[]>({ | ||
queryKey, | ||
queryFn: () => getNames({ addresses, chain }), | ||
gcTime: cacheTime, | ||
staleTime, | ||
enabled: enabled && addresses.length > 0, | ||
refetchOnWindowFocus, | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import { getChainPublicClient } from '@/core/network/getChainPublicClient'; | ||
import { isBase } from '@/core/utils/isBase'; | ||
import { isEthereum } from '@/core/utils/isEthereum'; | ||
import type { Basename, GetAvatarReturnType } from '@/identity/types'; | ||
import { mainnet } from 'viem/chains'; | ||
import { normalize } from 'viem/ens'; | ||
import { RESOLVER_ADDRESSES_BY_CHAIN_ID } from '../constants'; | ||
import { getBaseDefaultProfilePicture } from './getBaseDefaultProfilePicture'; | ||
import { isBasename } from './isBasename'; | ||
|
||
/** | ||
* Interface for the getAvatars function parameters | ||
*/ | ||
export type GetAvatars = { | ||
ensNames: string[]; | ||
chain?: typeof mainnet; | ||
} | ||
|
||
/** | ||
* An asynchronous function to fetch multiple Basenames or Ethereum Name Service (ENS) | ||
* avatars for a given array of ENS names in a single batch request. | ||
* It returns an array of avatar URLs in the same order as the input names. | ||
*/ | ||
export const getAvatars = async ({ | ||
ensNames, | ||
chain = mainnet, | ||
}: GetAvatars): Promise<GetAvatarReturnType[]> => { | ||
if (!ensNames || ensNames.length === 0) { | ||
return []; | ||
} | ||
|
||
const chainIsBase = isBase({ chainId: chain.id }); | ||
const chainIsEthereum = isEthereum({ chainId: chain.id }); | ||
const chainSupportsUniversalResolver = chainIsEthereum || chainIsBase; | ||
|
||
if (!chainSupportsUniversalResolver) { | ||
return Promise.reject( | ||
'ChainId not supported, avatar resolution is only supported on Ethereum and Base.', | ||
); | ||
} | ||
|
||
// Initialize results array | ||
const results: GetAvatarReturnType[] = Array(ensNames.length).fill(null); | ||
|
||
// Categorize names by type for optimized processing | ||
const basenameIndices: number[] = []; | ||
const normalIndices: number[] = []; | ||
|
||
ensNames.forEach((name, index) => { | ||
if (isBasename(name)) { | ||
basenameIndices.push(index); | ||
} else { | ||
normalIndices.push(index); | ||
} | ||
}); | ||
|
||
// Process Base chain avatars if applicable | ||
if (chainIsBase && basenameIndices.length > 0) { | ||
const client = getChainPublicClient(chain); | ||
|
||
try { | ||
// Create batch of calls for Base avatars | ||
const baseAvatarPromises = basenameIndices.map((index) => | ||
client.getEnsAvatar({ | ||
name: normalize(ensNames[index]), | ||
universalResolverAddress: RESOLVER_ADDRESSES_BY_CHAIN_ID[chain.id], | ||
}), | ||
); | ||
|
||
// Execute all Base avatar resolution calls | ||
const baseAvatarResults = await Promise.all(baseAvatarPromises); | ||
|
||
// Update results with Base avatars | ||
baseAvatarResults.forEach((avatar, i) => { | ||
const originalIndex = basenameIndices[i]; | ||
if (avatar) { | ||
results[originalIndex] = avatar; | ||
} | ||
}); | ||
} catch (error) { | ||
// This is a best effort attempt, so we continue to fallback | ||
console.error('Error resolving Base avatars in batch:', error); | ||
} | ||
} | ||
|
||
// Process mainnet avatars for all names | ||
const fallbackClient = getChainPublicClient(mainnet); | ||
|
||
// For all names, try mainnet resolution | ||
try { | ||
// Create batch of ENS avatar resolution calls | ||
const ensAvatarPromises = ensNames.map((name, index) => { | ||
// Skip if we already have a result | ||
if (results[index] !== null) { | ||
return Promise.resolve(null); | ||
} | ||
return fallbackClient.getEnsAvatar({ | ||
name: normalize(name), | ||
}); | ||
}); | ||
|
||
// Execute all ENS avatar resolution calls | ||
const ensAvatarResults = await Promise.all(ensAvatarPromises); | ||
|
||
// Update results with ENS avatars | ||
ensAvatarResults.forEach((avatar, index) => { | ||
if (avatar && results[index] === null) { | ||
results[index] = avatar; | ||
} | ||
}); | ||
} catch (error) { | ||
console.error('Error resolving ENS avatars in batch:', error); | ||
} | ||
|
||
// Apply default Base profile pictures for basenames that don't have avatars | ||
basenameIndices.forEach((index) => { | ||
if (results[index] === null) { | ||
results[index] = getBaseDefaultProfilePicture( | ||
ensNames[index] as Basename, | ||
); | ||
} | ||
}); | ||
|
||
return results; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import type { Address } from 'viem'; | ||
import { mainnet } from 'viem/chains'; | ||
import { getChainPublicClient } from '../../core/network/getChainPublicClient'; | ||
import { isBase } from '../../core/utils/isBase'; | ||
import { isEthereum } from '../../core/utils/isEthereum'; | ||
import L2ResolverAbi from '../abis/L2ResolverAbi'; | ||
import { RESOLVER_ADDRESSES_BY_CHAIN_ID } from '../constants'; | ||
import { convertReverseNodeToBytes } from './convertReverseNodeToBytes'; | ||
import type { Basename, GetNameReturnType } from '@/identity/types'; | ||
|
||
/** | ||
* Interface for the getNames function parameters | ||
*/ | ||
export type GetNames = { | ||
addresses: Address[]; | ||
chain?: typeof mainnet; | ||
} | ||
|
||
/** | ||
* An asynchronous function to fetch multiple Basenames or Ethereum Name Service (ENS) | ||
* names for a given array of Ethereum addresses in a single batch request. | ||
* It returns an array of ENS names in the same order as the input addresses. | ||
*/ | ||
export const getNames = async ({ | ||
addresses, | ||
chain = mainnet, | ||
}: GetNames): Promise<GetNameReturnType[]> => { | ||
if (!addresses || addresses.length === 0) { | ||
return []; | ||
} | ||
|
||
const chainIsBase = isBase({ chainId: chain.id }); | ||
const chainIsEthereum = isEthereum({ chainId: chain.id }); | ||
const chainSupportsUniversalResolver = chainIsEthereum || chainIsBase; | ||
|
||
if (!chainSupportsUniversalResolver) { | ||
return Promise.reject( | ||
'ChainId not supported, name resolution is only supported on Ethereum and Base.', | ||
); | ||
} | ||
|
||
const client = getChainPublicClient(chain); | ||
const results: GetNameReturnType[] = Array(addresses.length).fill(null); | ||
|
||
// Handle Base chain resolution | ||
if (chainIsBase) { | ||
try { | ||
// Create batch of calls for the multicall contract | ||
const calls = addresses.map((address) => ({ | ||
address: RESOLVER_ADDRESSES_BY_CHAIN_ID[chain.id], | ||
abi: L2ResolverAbi, | ||
functionName: 'name', | ||
args: [convertReverseNodeToBytes(address, chain.id)], | ||
})); | ||
|
||
// Execute batch request | ||
const batchResults = await client.multicall({ | ||
contracts: calls, | ||
allowFailure: true, | ||
}); | ||
|
||
// Process results | ||
batchResults.forEach((result, index) => { | ||
if (result.status === 'success' && result.result) { | ||
results[index] = result.result as Basename; | ||
} | ||
}); | ||
|
||
// If we have all results, return them | ||
if (results.every((result) => result !== null)) { | ||
return results; | ||
} | ||
} catch (error) { | ||
// This is a best effort attempt, so we continue to fallback | ||
console.error('Error resolving Base names in batch:', error); | ||
} | ||
} | ||
|
||
// Default fallback to mainnet for any unresolved names | ||
// ENS resolution is not well-supported on Base, so want to ensure that we fall back to mainnet | ||
const fallbackClient = getChainPublicClient(mainnet); | ||
|
||
// For addresses that don't have a result yet, try ENS resolution on mainnet | ||
const unresolvedIndices = results | ||
.map((result, index) => (result === null ? index : -1)) | ||
.filter((index) => index !== -1); | ||
|
||
if (unresolvedIndices.length > 0) { | ||
try { | ||
// Create batch of ENS resolution calls | ||
const ensPromises = unresolvedIndices.map((index) => | ||
fallbackClient.getEnsName({ | ||
address: addresses[index], | ||
}), | ||
); | ||
|
||
// Execute all ENS resolution calls | ||
const ensResults = await Promise.all(ensPromises); | ||
|
||
// Update results with ENS names | ||
ensResults.forEach((ensName, i) => { | ||
const originalIndex = unresolvedIndices[i]; | ||
results[originalIndex] = ensName; | ||
}); | ||
} catch (error) { | ||
console.error('Error resolving ENS names in batch:', error); | ||
} | ||
} | ||
|
||
return results; | ||
}; |