Created
September 15, 2024 09:33
-
-
Save Yuripetusko/0469f2d993fe0688bb8dc8d09cd9fd69 to your computer and use it in GitHub Desktop.
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
import { | |
type Address, | |
type ContractFunctionParameters, | |
createPublicClient, | |
encodePacked, | |
http, | |
keccak256, | |
namehash, | |
zeroAddress, | |
} from 'viem'; | |
import L2ResolverAbi from 'lib/abis/L2ResolverAbi'; | |
import { base, mainnet } from 'viem/chains'; | |
import { useQuery } from '@tanstack/react-query'; | |
import { RMRK_IPFS_GATEWAY_URL } from 'lib/constants/app'; | |
import { getIpfsCidFromGatewayUrl, sanitizeIpfsUrl } from '@rmrk-team/ipfs-utils'; | |
import { BLASTAPI_SINGULAR_PROD_API_KEY } from 'lib/wagmi/wagmi-config'; | |
export type Basename = `${string}.base.eth`; | |
export const BASENAME_L2_RESOLVER_ADDRESS = '0xC6d566A56A1aFf6508b41f6c90ff131615583BCD'; | |
export enum BasenameTextRecordKeys { | |
Description = 'description', | |
Keywords = 'keywords', | |
Url = 'url', | |
Email = 'email', | |
Phone = 'phone', | |
Github = 'com.github', | |
Twitter = 'com.twitter', | |
Farcaster = 'xyz.farcaster', | |
Lens = 'xyz.lens', | |
Telegram = 'org.telegram', | |
Discord = 'com.discord', | |
Avatar = 'avatar', | |
} | |
export const textRecordsKeysEnabled = [ | |
BasenameTextRecordKeys.Description, | |
BasenameTextRecordKeys.Keywords, | |
BasenameTextRecordKeys.Url, | |
BasenameTextRecordKeys.Github, | |
BasenameTextRecordKeys.Email, | |
BasenameTextRecordKeys.Phone, | |
BasenameTextRecordKeys.Twitter, | |
BasenameTextRecordKeys.Farcaster, | |
BasenameTextRecordKeys.Lens, | |
BasenameTextRecordKeys.Telegram, | |
BasenameTextRecordKeys.Discord, | |
BasenameTextRecordKeys.Avatar, | |
]; | |
const baseClient = createPublicClient({ | |
chain: base, | |
transport: http(`https://base-mainnet.blastapi.io/${BLASTAPI_SINGULAR_PROD_API_KEY}`), | |
}); | |
export async function getBasenameAvatar(basename: Basename) { | |
const avatar = await baseClient.getEnsAvatar({ | |
name: basename, | |
universalResolverAddress: BASENAME_L2_RESOLVER_ADDRESS, | |
}); | |
return avatar; | |
} | |
export function buildBasenameTextRecordContract( | |
basename: Basename, | |
key: BasenameTextRecordKeys, | |
): ContractFunctionParameters { | |
return { | |
abi: L2ResolverAbi, | |
address: BASENAME_L2_RESOLVER_ADDRESS, | |
args: [namehash(basename), key], | |
functionName: 'text', | |
}; | |
} | |
// Get a single TextRecord | |
export async function getBasenameTextRecord(basename: Basename, key: BasenameTextRecordKeys) { | |
const contractParameters = buildBasenameTextRecordContract(basename, key); | |
const textRecord = await baseClient.readContract(contractParameters); | |
return textRecord as string; | |
} | |
// Get a all TextRecords | |
export async function getBasenameTextRecords(basename: Basename) { | |
const readContracts: ContractFunctionParameters[] = textRecordsKeysEnabled.map((key) => | |
buildBasenameTextRecordContract(basename, key), | |
); | |
const textRecords = await baseClient.multicall({ | |
contracts: readContracts, | |
}); | |
return textRecords; | |
} | |
/** | |
* Convert an chainId to a coinType hex for reverse chain resolution | |
*/ | |
export const convertChainIdToCoinType = (chainId: number): string => { | |
// L1 resolvers to addr | |
if (chainId === mainnet.id) { | |
return 'addr'; | |
} | |
const cointype = (0x80000000 | chainId) >>> 0; | |
return cointype.toString(16).toLocaleUpperCase(); | |
}; | |
/** | |
* Convert an address to a reverse node for ENS resolution | |
*/ | |
export const convertReverseNodeToBytes = (address: Address, chainId: number) => { | |
const addressFormatted = address.toLocaleLowerCase() as Address; | |
const addressNode = keccak256(addressFormatted.substring(2) as Address); | |
const chainCoinType = convertChainIdToCoinType(chainId); | |
const baseReverseNode = namehash(`${chainCoinType.toLocaleUpperCase()}.reverse`); | |
const addressReverseNode = keccak256( | |
encodePacked(['bytes32', 'bytes32'], [baseReverseNode, addressNode]), | |
); | |
return addressReverseNode; | |
}; | |
export async function getBasename(address: Address) { | |
const addressReverseNode = convertReverseNodeToBytes(address, base.id); | |
const basename = await baseClient.readContract({ | |
abi: L2ResolverAbi, | |
address: BASENAME_L2_RESOLVER_ADDRESS, | |
functionName: 'name', | |
args: [addressReverseNode], | |
}); | |
if (basename) { | |
return basename as Basename; | |
} | |
return undefined; | |
} | |
export const useBaseNameAndAvatar = ({ | |
address, | |
isEnabled = true, | |
}: { | |
address?: Address; | |
isEnabled?: boolean; | |
}) => { | |
return useQuery({ | |
queryKey: ['basename-avatar', address ?? zeroAddress], | |
queryFn: async () => { | |
if (!address) { | |
return null; | |
} | |
const basename = await getBasename(address); | |
if (!basename) { | |
return null; | |
} | |
let avatar = await getBasenameAvatar(basename); | |
if (avatar) { | |
const ipfsCid = getIpfsCidFromGatewayUrl(avatar); | |
avatar = ipfsCid ? sanitizeIpfsUrl(`ipfs://${ipfsCid}`, RMRK_IPFS_GATEWAY_URL) : avatar; | |
} | |
return { basename, avatar }; | |
}, | |
enabled: !!address && isEnabled, | |
staleTime: 1 * 60 * 60 * 1000, | |
gcTime: 1 * 60 * 60 * 1000, | |
}); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment