Skip to content

Commit

Permalink
Add space.id domains
Browse files Browse the repository at this point in the history
  • Loading branch information
serg-plusplus committed Nov 15, 2024
1 parent cd12ce0 commit feda6da
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 75 deletions.
13 changes: 9 additions & 4 deletions src/app/components/elements/ContactAutocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { FieldMetaState } from "react-final-form";
import * as Popover from "@radix-ui/react-popover";
import Fuse from "fuse.js";
import { mergeRefs } from "react-merge-refs";
import { useThrottledCallback } from "use-debounce";
import { TReplace } from "lib/ext/i18n/react";
import { useOnScreen } from "lib/react-hooks/useOnScreen";
import { usePasteFromClipboard } from "lib/react-hooks/usePasteFromClipboard";
Expand Down Expand Up @@ -159,13 +160,17 @@ const ContactAutocomplete = forwardRef<

const { paste } = usePasteFromClipboard(setValue);

const { getAddressByEns, watchEns } = useEns();
const { getAddressByRns, watchRns } = useRns();
const { watchEns } = useEns();
const { watchRns } = useRns();

useEffect(() => {
const watchDomain = useThrottledCallback(() => {
watchEns(value, setValue);
watchRns(value, setValue);
}, [value, setValue, getAddressByEns, watchEns, getAddressByRns, watchRns]);
}, 500);

useEffect(() => {
watchDomain();
}, [watchDomain, value]);

const pasteButton = useMemo(() => {
return (
Expand Down
11 changes: 10 additions & 1 deletion src/app/components/elements/SmallContactCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,16 @@ const SmallContactCard: FC<SmallContactCardProps> = ({
className={classNames("pl-2 flex items-center", className)}
onClick={() => upsertContact({ address })}
>
<PlusIcon className="mr-1 w-4 h-auto" />
<WalletAvatar
seed={address}
onlyEns
fallback={<PlusIcon className="mr-1 w-4 h-auto" />}
className={classNames(
"-my-1 h-4 w-4 min-w-4 mr-1",
"bg-black/40",
"rounded",
)}
/>
Save contact
</InputLabelAction>
);
Expand Down
113 changes: 63 additions & 50 deletions src/app/components/elements/WalletAvatar.tsx
Original file line number Diff line number Diff line change
@@ -1,63 +1,76 @@
import { FC, HTMLAttributes, memo, useEffect, useState } from "react";
import {
FC,
HTMLAttributes,
memo,
ReactNode,
useEffect,
useState,
} from "react";
import AutoIcon from "./AutoIcon";
import Avatar from "app/components/elements/Avatar";

import { useEns } from "app/hooks";

type WalletAvatarProps = HTMLAttributes<HTMLDivElement> & {
seed: string;
onlyEns?: boolean;
fallback?: ReactNode;
};

const WalletAvatar: FC<WalletAvatarProps> = memo(({ seed, className }) => {
const { getEnsName, getEnsAvatar } = useEns();

const [ensAvatar, setEnsAvatar] = useState<string | null>(null);

useEffect(() => {
const isValidEthereumAddress = (seed: string) => {
const ethereumAddressRegex = /^(0x)?[0-9a-fA-F]{40}$/;
return ethereumAddressRegex.test(seed);
};

const fetchEnsName = async () => {
try {
const name = await getEnsName(seed);
if (name) {
const avatar = await getEnsAvatar(name);
setEnsAvatar(avatar);
} else {
setEnsAvatar(null);
const WalletAvatar: FC<WalletAvatarProps> = memo(
({ seed, onlyEns, fallback, className }) => {
const { getEnsName, getEnsAvatar } = useEns();

const [ensAvatar, setEnsAvatar] = useState<string | null>(null);

useEffect(() => {
const isValidEthereumAddress = (seed: string) => {
const ethereumAddressRegex = /^(0x)?[0-9a-fA-F]{40}$/;
return ethereumAddressRegex.test(seed);
};

const fetchEnsName = async () => {
try {
const name = await getEnsName(seed);
if (name) {
const avatar = await getEnsAvatar(name);
setEnsAvatar(avatar);
} else {
setEnsAvatar(null);
}
} catch (error) {
console.error(error);
}
} catch (error) {
console.error(error);
};

if (isValidEthereumAddress(seed)) {
fetchEnsName();
}
};

if (isValidEthereumAddress(seed)) {
fetchEnsName();
}
}, [getEnsName, getEnsAvatar, seed]);

return (
<>
{ensAvatar ? (
<Avatar
src={ensAvatar}
className={className}
imageClassName="rounded-md"
alt={"ensAvatar"}
withBorder={false}
/>
) : (
<AutoIcon
seed={seed}
source="dicebear"
type="personas"
className={className}
/>
)}
</>
);
});
}, [getEnsName, getEnsAvatar, seed]);

return (
<>
{ensAvatar ? (
<Avatar
src={ensAvatar}
className={className}
imageClassName="rounded-md"
alt={"ensAvatar"}
withBorder={false}
/>
) : !onlyEns ? (
<AutoIcon
seed={seed}
source="dicebear"
type="personas"
className={className}
/>
) : (
fallback
)}
</>
);
},
);

export default WalletAvatar;
73 changes: 58 additions & 15 deletions src/app/hooks/ens.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
import { useMemo, useCallback } from "react";
import { getClientProvider } from "core/client";
import { AvatarResolver } from "@ensdomains/ens-avatar";
import { isAddress } from "ethers";
import { ZERO_ADDRESSES } from "core/common";

const NAME_SPACES = [
".eth",
".bnb",
".gno",
".arb",
// ".manta",
// ".cake",
// ".zeta",
// ".ll",
// ".mint",
// ".mode",
// ".inj",
// ".taiko",
];

const ONE_DAY = 24 * 60 * 60 * 1000;

Expand Down Expand Up @@ -28,14 +45,16 @@ const useEns = () => {
return parsedData.ensName;
} else {
const ensName = await provider.lookupAddress(address);
const name = ensName;

if (ensName) {
const data = {
ensName,
expirationTimestamp: Date.now() + ONE_DAY,
};
localStorage.setItem(`ENS_${address}`, JSON.stringify(data));
return ensName;
const data = {
ensName: name || null,
expirationTimestamp: Date.now() + ONE_DAY,
};
localStorage.setItem(`ENS_${address}`, JSON.stringify(data));

if (name) {
return name;
} else {
return null;
}
Expand All @@ -46,12 +65,34 @@ const useEns = () => {

const getAddressByEns = useCallback(
async (ensName: string) => {
const address = await provider.resolveName(ensName);
if (address) {
return address;
} else {
return null;
try {
// ENS
const addressEns = await provider
.resolveName(ensName)
.catch(() => null);

if (addressEns) {
return addressEns;
}

// Space ID
let tld = ensName.split(".").pop();
if (tld === "arb") tld = "arb1";
const address = await fetch(
`https://api.prd.space.id/v1/getAddress?tld=${tld}&domain=${ensName}`,
)
.then((res) => res.json())
.then((data) => data?.address || null)
.catch(console.warn);

if (isAddress(address) && !ZERO_ADDRESSES.has(address.toLowerCase())) {
return address;
}
} catch (err) {
console.warn(err);
}

return null;
},
[provider],
);
Expand All @@ -65,7 +106,9 @@ const useEns = () => {
} else {
//@ts-expect-error: Should expect JsonRpcProvider
const resolver = new AvatarResolver(provider);
const imageUrl = await resolver.getAvatar(ensName, {});
const imageUrl = await resolver
.getAvatar(ensName, {})
.catch(() => null);

if (imageUrl) {
const imageDataUrl = await toDataURL(imageUrl);
Expand All @@ -89,10 +132,10 @@ const useEns = () => {
const watchEns = useCallback(
async (value: any, cb: (address: string) => void) => {
const ethereumAddressOrENSRegex =
/^(0x[a-fA-F0-9]{40})|([a-zA-Z0-9-]+\.eth)$/;
/^(0x[a-fA-F0-9]{40})|([a-zA-Z0-9-_]+\.[a-zA-Z]+)$/;
if (value && typeof value == "string") {
const isValid = ethereumAddressOrENSRegex.test(value);
if (isValid && value.includes(".eth")) {
if (isValid && NAME_SPACES.some((ns) => value.endsWith(ns))) {
const response = await getAddressByEns(value);
if (response) {
cb(response);
Expand Down
11 changes: 6 additions & 5 deletions src/app/hooks/rns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,13 @@ const useRns = () => {
} else {
const rnsName = await lookupAddress(address);

const data = {
rnsName,
expirationTimestamp: Date.now() + ONE_DAY,
};
localStorage.setItem(`RNS_${address}`, JSON.stringify(data));

if (rnsName) {
const data = {
rnsName,
expirationTimestamp: Date.now() + ONE_DAY,
};
localStorage.setItem(`RNS_${address}`, JSON.stringify(data));
return rnsName;
} else {
return null;
Expand Down

0 comments on commit feda6da

Please sign in to comment.