Skip to content

Instantly share code, notes, and snippets.

@Yuripetusko
Created September 15, 2024 09:33
Show Gist options
  • Save Yuripetusko/0469f2d993fe0688bb8dc8d09cd9fd69 to your computer and use it in GitHub Desktop.
Save Yuripetusko/0469f2d993fe0688bb8dc8d09cd9fd69 to your computer and use it in GitHub Desktop.
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