Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: reinstate fetching web3 user names #3638

Merged
merged 8 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/lucky-kids-sniff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@venusprotocol/evm": minor
---

fetch web3 domain names for addresses
1 change: 1 addition & 0 deletions apps/evm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"@sentry/vite-plugin": "^2.20.0",
"@tanstack/react-query": "^5.48.0",
"@wagmi/core": "^2.11.6",
"@yornaath/batshit": "^0.10.1",
"bignumber.js": "^9.1.1",
"buffer": "^6.0.3",
"clsx": "^2.0.0",
Expand Down
8 changes: 8 additions & 0 deletions apps/evm/src/clients/api/__mocks__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,14 @@ export const useGetIsolatedPoolVTokenLiquidationThreshold = vi.fn(() =>
}),
);

export const getAddressDomainName = vi.fn(async () => undefined);
export const useGetAddressDomainName = vi.fn(() =>
useQuery({
queryKey: [FunctionKey.GET_ADDRESS_DOMAIN_NAME],
queryFn: getAddressDomainName,
}),
);

export const getXvsVaultPendingWithdrawalsBalance = vi.fn(async () => ({
balanceMantissa: 0,
}));
Expand Down
4 changes: 4 additions & 0 deletions apps/evm/src/clients/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,10 @@ export { default as getPoolDelegateApprovalStatus } from './queries/getPoolDeleg
export * from './queries/getPoolDelegateApprovalStatus';
export { default as useGetPoolDelegateApprovalStatus } from './queries/getPoolDelegateApprovalStatus/useGetPoolDelegateApprovalStatus';

export { default as getAddressDomainName } from './queries/getAddressDomainName';
export * from './queries/getAddressDomainName';
export { default as useGetAddressDomainName } from './queries/getAddressDomainName/useGetAddressDomainName';

export * from './queries/getPaymasterInfo';
export { default as useGetPaymasterInfo } from './queries/getPaymasterInfo/useGetPaymasterInfo';

Expand Down
52 changes: 52 additions & 0 deletions apps/evm/src/clients/api/queries/getAddressDomainName/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { create, windowScheduler } from '@yornaath/batshit';
import type { ChainId } from 'types';
import { restService } from 'utilities';
import type { Address } from 'viem';

const getDomainNameFromApi = async ({
addresses,
chainIds,
}: { addresses: Address[]; chainIds?: ChainId[] }) => {
const response = await restService<GetApiDomainNameResponse>({
endpoint: '/web3-domain-name',
method: 'GET',
params: {
addresses: JSON.stringify([...new Set(addresses)]),
chainIds: JSON.stringify([...new Set(chainIds)]),
},
});

if (response.data && 'error' in response.data) {
throw new Error(response.data.error);
}

return response.data;
};

const domainNamesBatcher = create({
fetcher: async (queries: { accountAddress: Address; chainId: ChainId }[]) =>
getDomainNameFromApi({
addresses: queries.map(q => q.accountAddress),
chainIds: queries.map(q => q.chainId),
}),
resolver: (data, query) => data?.[query.accountAddress],
scheduler: windowScheduler(10),
});

type ChainIdDomainNameMap = { [Key in ChainId]?: string | null };
type GetApiDomainNameResponse = Record<Address, ChainIdDomainNameMap>;

export interface GetAddressDomainNameInput {
accountAddress: Address;
chainId: ChainId;
}

export type GetAddressDomainNameOutput = ChainIdDomainNameMap;

const getAddressDomainName = async ({ accountAddress, chainId }: GetAddressDomainNameInput) => {
const response = await domainNamesBatcher.fetch({ accountAddress, chainId });

return response || {};
};

export default getAddressDomainName;
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { type QueryObserverOptions, useQuery } from '@tanstack/react-query';

import getAddressDomainName, {
type GetAddressDomainNameInput,
type GetAddressDomainNameOutput,
} from 'clients/api/queries/getAddressDomainName';
import FunctionKey from 'constants/functionKey';

type UseGetAddressDomainNameInput = GetAddressDomainNameInput;

export type UseGetAddressDomainNameQueryKey = [
FunctionKey.GET_ADDRESS_DOMAIN_NAME,
GetAddressDomainNameInput,
];

type Options = QueryObserverOptions<
GetAddressDomainNameOutput,
Error,
GetAddressDomainNameOutput,
GetAddressDomainNameOutput,
UseGetAddressDomainNameQueryKey
>;

const useGetAddressDomainName = (
{ accountAddress, chainId }: UseGetAddressDomainNameInput,
options?: Partial<Options>,
) => {
const queryKey: UseGetAddressDomainNameQueryKey = [
FunctionKey.GET_ADDRESS_DOMAIN_NAME,
{
accountAddress,
chainId,
},
];

return useQuery({
queryKey: queryKey,
queryFn: () => getAddressDomainName({ accountAddress, chainId }),
...options,
});
};

export default useGetAddressDomainName;
4 changes: 2 additions & 2 deletions apps/evm/src/components/EllipseAddress/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import type { Breakpoint } from './types';

export * from './types';

interface AddressProps {
export interface EllipseAddressProps {
address: string;
ellipseBreakpoint?: Breakpoint;
className?: string;
}

export const EllipseAddress: React.FC<AddressProps> = ({
export const EllipseAddress: React.FC<EllipseAddressProps> = ({
className,
address,
ellipseBreakpoint,
Expand Down
66 changes: 66 additions & 0 deletions apps/evm/src/components/Icon/icons/ensLogo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type { SVGProps } from 'react';

const SvgEnsLogo = (props: SVGProps<SVGSVGElement>) => (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g clip-path="url(#clip0_47_1749)">
<path
d="M3.49441 6.7707C3.62296 7.04388 3.94435 7.59024 3.94435 7.59024L7.6242 1.5L4.04076 4.0068C3.83186 4.15142 3.6551 4.34425 3.52655 4.56922C3.18909 5.2602 3.18909 6.06366 3.49441 6.7707Z"
fill="url(#paint0_linear_47_1749)"
/>
<path
d="M2.04824 8.7634C2.12858 9.93646 2.72314 11.0292 3.65516 11.7362L7.62425 14.5001C7.62425 14.5001 5.13353 10.9167 3.04453 7.34931C2.83563 6.97972 2.691 6.56192 2.62673 6.12805C2.59459 5.93522 2.59459 5.74239 2.62673 5.54956C2.57852 5.64598 2.46603 5.85488 2.46603 5.85488C2.25713 6.28874 2.11251 6.75475 2.03217 7.23683C1.98396 7.75104 1.98396 8.26526 2.04824 8.7634Z"
fill="#A0A8D4"
/>
<path
d="M12.1718 9.24531C12.0432 8.97214 11.7218 8.42578 11.7218 8.42578L8.04199 14.4999L11.6415 12.0092C11.8504 11.8646 12.0272 11.6718 12.1557 11.4468C12.4771 10.7558 12.4932 9.95236 12.1718 9.24531Z"
fill="url(#paint1_linear_47_1749)"
/>
<path
d="M13.6341 7.23671C13.5538 6.06366 12.9592 4.97095 12.0272 4.26391L8.05811 1.5C8.05811 1.5 10.5488 5.08344 12.6378 8.6508C12.8467 9.02039 12.9914 9.43819 13.0556 9.87206C13.0878 10.0649 13.0878 10.2577 13.0556 10.4506C13.1038 10.3541 13.2163 10.1452 13.2163 10.1452C13.4252 9.71137 13.5698 9.24536 13.6502 8.77936C13.6823 8.24907 13.6823 7.75093 13.6341 7.23671Z"
fill="#A0A8D4"
/>
<path
d="M3.52673 4.56922C3.65528 4.34425 3.81597 4.15142 4.04094 4.0068L7.62438 1.5L3.94453 7.57417C3.94453 7.57417 3.62314 7.02781 3.49459 6.75464C3.18927 6.06366 3.18927 5.2602 3.52673 4.56922ZM2.04836 8.76329C2.1287 9.93634 2.72326 11.029 3.65528 11.7361L7.62438 14.5C7.62438 14.5 5.13365 10.9166 3.04465 7.3492C2.83575 6.9796 2.69113 6.5618 2.62685 6.12794C2.59471 5.9351 2.59471 5.74227 2.62685 5.54944C2.57864 5.64586 2.46616 5.85476 2.46616 5.85476C2.25726 6.28863 2.11263 6.75464 2.03229 7.23671C1.98408 7.75093 1.98408 8.26514 2.04836 8.76329ZM12.172 9.24536C12.0434 8.97219 11.722 8.42583 11.722 8.42583L8.04218 14.5L11.6417 12.0093C11.8506 11.8646 12.0273 11.6718 12.1559 11.4468C12.4773 10.7559 12.4934 9.95241 12.172 9.24536ZM13.6182 7.25278C13.5379 6.07973 12.9433 4.98702 12.0113 4.27998L8.05825 1.5C8.05825 1.5 10.549 5.08344 12.638 8.6508C12.8469 9.0204 12.9915 9.4382 13.0558 9.87206C13.0879 10.0649 13.0879 10.2577 13.0558 10.4506C13.104 10.3541 13.2165 10.1452 13.2165 10.1452C13.4254 9.71137 13.57 9.24536 13.6503 8.77936C13.6825 8.24907 13.6825 7.75093 13.6182 7.25278Z"
fill="#AAB3CA"
/>
</g>
<defs>
<linearGradient
id="paint0_linear_47_1749"
x1="7.79157"
y1="1.65235"
x2="3.07018"
y2="6.77069"
gradientUnits="userSpaceOnUse"
>
<stop offset="0.58" stop-color="#A0A8D4" />
<stop offset="0.73" stop-color="#8791C7" />
<stop offset="0.91" stop-color="#6470B4" />
</linearGradient>
<linearGradient
id="paint1_linear_47_1749"
x1="7.89072"
y1="14.3583"
x2="12.6103"
y2="9.24361"
gradientUnits="userSpaceOnUse"
>
<stop offset="0.58" stop-color="#A0A8D4" />
<stop offset="0.73" stop-color="#8791C7" />
<stop offset="0.91" stop-color="#6470B4" />
</linearGradient>
<clipPath id="clip0_47_1749">
<rect width="11.6823" height="13" fill="white" transform="translate(2 1.5)" />
</clipPath>
</defs>
</svg>
);

export default SvgEnsLogo;
2 changes: 2 additions & 0 deletions apps/evm/src/components/Icon/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,5 @@ export { default as document } from './document';
export { default as gas } from './gas';
export { default as gasSlashed } from './gasSlashed';
export { default as gasSad } from './gasSad';
export { default as ensLogo } from './ensLogo';
export { default as spaceIdLogo } from './spaceIdLogo';
25 changes: 25 additions & 0 deletions apps/evm/src/components/Icon/icons/spaceIdLogo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { SVGProps } from 'react';

const SvgSpaceId = (props: SVGProps<SVGSVGElement>) => (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M9.01764 10.1264C9.01764 9.61757 9.42777 9.19985 9.94422 9.19985H10.3164C10.8252 9.19985 11.2354 9.60997 11.2354 10.1264V10.4986C11.2354 11.0074 10.8252 11.4252 10.3088 11.4252H9.93663C9.42777 11.4252 9.01764 11.015 9.01764 10.4986V10.1264ZM9.94422 5.88086C9.43536 5.88086 9.02523 6.29099 9.02523 6.80744V7.17959C9.02523 7.68845 9.43536 8.10618 9.95182 8.10618H10.324C10.8328 8.10618 11.243 7.69605 11.243 7.17959V6.80744C11.243 6.29858 10.8328 5.88086 10.3164 5.88086H9.94422Z"
fill="#AAB3CA"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5.69114 2C3.6481 2 2 3.6557 2 5.69114V10.3089C2 12.3519 3.6557 14 5.69114 14H10.3089C12.3519 14 14 12.3443 14 10.3089V5.69114C14 3.6481 12.3443 2 10.3089 2H5.69114ZM10.3089 3.47342H5.69114C4.46835 3.47342 3.47342 4.46835 3.47342 5.69114V10.3089C3.47342 11.5316 4.46835 12.5266 5.69114 12.5266H10.3089C11.5316 12.5266 12.5266 11.5316 12.5266 10.3089V5.69114C12.5266 4.46835 11.5316 3.47342 10.3089 3.47342Z"
fill="#AAB3CA"
/>
</svg>
);

export default SvgSpaceId;
12 changes: 12 additions & 0 deletions apps/evm/src/components/Username/UsernameSpan.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { cn } from 'utilities';

interface UsernameSpanProps {
className?: string;
username: string;
}

const UsernameSpan = ({ className, username }: UsernameSpanProps) => (
<span className={cn('truncate', className)}>{username}</span>
);

export default UsernameSpan;
104 changes: 104 additions & 0 deletions apps/evm/src/components/Username/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { useGetAddressDomainName } from 'clients/api';
import { Icon, Tooltip } from 'components';
import EllipseAddress, { type EllipseAddressProps } from 'components/EllipseAddress';
import { CopyAddressButton } from 'containers/CopyAddressButton';
import { useTranslation } from 'libs/translations';
import { useChainId } from 'libs/wallet';
import { ChainId } from 'types';
import { truncateAddress } from 'utilities';
import type { Address } from 'viem';
import UsernameSpan from './UsernameSpan';

type UsernameProps = {
showProvider?: boolean;
showTooltip?: boolean;
showCopyAddress?: boolean;
shouldEllipseAddress?: boolean;
children?: (props: { innerContent: React.ReactNode }) => React.ReactNode;
} & EllipseAddressProps;

export const Username: React.FC<UsernameProps> = ({
address,
children,
className,
showCopyAddress = false,
showProvider = true,
showTooltip = true,
shouldEllipseAddress = true,
...ellipseAddressProps
}) => {
const { t } = useTranslation();
const { chainId } = useChainId();
const { data: domainNames, isLoading: isGetAddressDomainNameLoading } = useGetAddressDomainName(
{
accountAddress: address as Address,
chainId,
},
{
enabled: !!address,
},
);

const chainDomainName = domainNames?.[chainId];
// Ethereum and Sepolia use ENS, other chains use SpaceID
const providerIcon =
chainId === ChainId.ETHEREUM || chainId === ChainId.SEPOLIA ? 'ensLogo' : 'spaceIdLogo';
const providerText =
chainId === ChainId.ETHEREUM || chainId === ChainId.SEPOLIA
? t('web3DomainNames.providers.ens')
: t('web3DomainNames.providers.spaceId');

const addressComponent = shouldEllipseAddress ? (
<EllipseAddress className={className} address={address} {...ellipseAddressProps} />
) : (
<UsernameSpan className={className} username={address} />
);

let dom = children ? children({ innerContent: addressComponent }) : addressComponent;

if (!isGetAddressDomainNameLoading && chainDomainName) {
const chainDomainNameSpan = <UsernameSpan className={className} username={chainDomainName} />;
const domainNameComponent = children
? children({ innerContent: chainDomainNameSpan })
: chainDomainNameSpan;
dom = (
<div className="flex flex-row items-center text-nowrap space-x-1 mr-1">
{showProvider && (
<Tooltip title={providerText}>
<Icon className="cursor-pointer" name={providerIcon} />
</Tooltip>
)}
{domainNameComponent}
{showTooltip && (
<Tooltip
title={
<div className="flex flex-col text-center">
<span className="text-center">{chainDomainName}</span>
<span className="md:hidden">
{t('web3DomainNames.tooltip.address', { address: truncateAddress(address) })}
</span>
<span className="hidden md:block">
{t('web3DomainNames.tooltip.address', { address })}
</span>
</div>
}
className="inline-flex"
>
<Icon className="cursor-pointer " name="info" />
</Tooltip>
)}
</div>
);
}

return (
<>
{dom}
{showCopyAddress && (
<CopyAddressButton className="shrink-0" address={address} showTooltip={!!chainDomainName} />
)}
</>
);
};

export default Username;
1 change: 1 addition & 0 deletions apps/evm/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from './Checkbox';
export * from './Chip';
export * from './Countdown';
export * from './Delimiter';
export * from './Username';
export * from './EllipseAddress';
export * from './ApproveTokenSteps';
export * from './ApprovalSteps';
Expand Down
1 change: 1 addition & 0 deletions apps/evm/src/constants/functionKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ enum FunctionKey {
GET_PAYMASTER_INFO = 'GET_PAYMASTER_INFO',
GET_MARKETS = 'GET_MARKETS',
GET_POOLS = 'GET_POOLS',
GET_ADDRESS_DOMAIN_NAME = 'GET_ADDRESS_DOMAIN_NAME',

// Mutations
MINT_VAI = 'MINT_VAI',
Expand Down
Loading