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

Add RNS Rootstock name service #429

Merged
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
6 changes: 4 additions & 2 deletions src/app/components/elements/ContactAutocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
LOAD_MORE_ON_CONTACTS_DROPDOWN_FROM_END,
} from "app/defaults";
import { useContacts } from "app/hooks/contacts";
import { useAccounts, useEns } from "app/hooks";
import { useAccounts, useEns, useRns } from "app/hooks";
import ScrollAreaContainer from "./ScrollAreaContainer";
import AddressField, { AddressFieldProps } from "./AddressField";
import WalletName from "./WalletName";
Expand Down Expand Up @@ -160,10 +160,12 @@ const ContactAutocomplete = forwardRef<
const { paste } = usePasteFromClipboard(setValue);

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

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

const pasteButton = useMemo(() => {
return (
Expand Down
9 changes: 6 additions & 3 deletions src/app/components/elements/WalletName.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import classNames from "clsx";
import { TReplace } from "lib/ext/i18n/react";

import { Account, AccountSource } from "core/types";
import { useEns } from "app/hooks";
import { useEns, useRns } from "app/hooks";

import { ReactComponent as GoogleIcon } from "app/icons/google.svg";
import { ReactComponent as FacebookIcon } from "app/icons/facebook.svg";
Expand All @@ -27,21 +27,24 @@ const WalletName: FC<WalletNameProps> = ({
iconClassName,
}) => {
const { getEnsName } = useEns();
const { getRnsName } = useRns();

const [ensName, setEnsName] = useState<string | null>(null);

useEffect(() => {
const fetchEnsName = async () => {
try {
const name = await getEnsName(wallet.address);
const name =
(await getEnsName(wallet.address)) ||
(await getRnsName(wallet.address));
setEnsName(name);
} catch (error) {
console.error(error);
}
};

fetchEnsName();
}, [getEnsName, wallet.address]);
}, [getEnsName, getRnsName, wallet.address]);

return (
<span
Expand Down
1 change: 1 addition & 0 deletions src/app/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export * from "./tokens";
export * from "./nftMetadata";
export * from "./ramp";
export * from "./ens";
export * from "./rns";
export * from "./hideToken";
141 changes: 141 additions & 0 deletions src/app/hooks/rns.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { useMemo, useCallback } from "react";
import { JsonRpcProvider } from "@ethersproject/providers";
import { Contract } from "@ethersproject/contracts";
import { AddressZero } from "@ethersproject/constants";
import { namehash } from "@ethersproject/hash";

const ONE_DAY = 24 * 60 * 60 * 1000;

const ROOTSTOCK_RPC_NODE = "https://public-node.rsk.co";

// REF: https://developers.rsk.co/rif/rns/architecture/registry/
const RNS_REGISTRY_ADDRESS = "0xcb868aeabd31e2b66f74e9a55cf064abb31a4ad5";

const stripHexPrefix = (hex: string): string => hex.slice(2);

const RNS_REGISTRY_ABI = [
"function resolver(bytes32 node) public view returns (address)",
];

const RNS_ADDR_RESOLVER_ABI = [
"function addr(bytes32 node) public view returns (address)",
];

const RNS_NAME_RESOLVER_ABI = [
"function name(bytes32 node) external view returns (string)",
];

const RNSProvider = new JsonRpcProvider(ROOTSTOCK_RPC_NODE);
const rnsRegistryContract = new Contract(
RNS_REGISTRY_ADDRESS,
RNS_REGISTRY_ABI,
RNSProvider,
);

const resolveRnsName = async (name: string): Promise<string | null> => {
const nameHash = namehash(name);
const resolverAddress = await rnsRegistryContract.resolver(nameHash);

if (resolverAddress === AddressZero) {
return null;
}

const addrResolverContract = new Contract(
resolverAddress,
RNS_ADDR_RESOLVER_ABI,
RNSProvider,
);

const address = await addrResolverContract.addr(nameHash);

if (address === undefined || address === null) {
return null;
}

return address.toLowerCase();
};

const lookupAddress = async (address: string): Promise<string | null> => {
const reverseRecordHash = namehash(`${stripHexPrefix(address)}.addr.reverse`);

const resolverAddress = await rnsRegistryContract.resolver(reverseRecordHash);

if (resolverAddress === AddressZero) {
return null;
}

const nameResolverContract = new Contract(
resolverAddress,
RNS_NAME_RESOLVER_ABI,
RNSProvider,
);

const name = await nameResolverContract.name(reverseRecordHash);

if (name === undefined) {
return null;
}

return name;
};

const useRns = () => {
const getRnsName = useCallback(async (address: string) => {
const rnsNameLS = localStorage.getItem(`RNS_${address}`);
const parsedData = rnsNameLS ? JSON.parse(rnsNameLS) : null;
if (parsedData && parsedData.expirationTimestamp > Date.now()) {
return parsedData.ensName;
} else {
const rnsName = await lookupAddress(address);

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

const getAddressByRns = useCallback(async (rnsName: string) => {
const address = await resolveRnsName(rnsName);

if (address) {
return address;
} else {
return null;
}
}, []);

const watchRns = useCallback(
async (value: any, cb: (address: string) => void) => {
const ethereumAddressOrRNSRegex =
/^(0x[a-fA-F0-9]{40})|([a-zA-Z0-9-]+\.rsk)$/;
if (value && typeof value == "string") {
const isValid = ethereumAddressOrRNSRegex.test(value);
if (isValid && value.includes(".rsk")) {
const response = await getAddressByRns(value);
if (response) {
cb(response);
}
}
}
},
[getAddressByRns],
);

return useMemo(
() => ({
getRnsName,
getAddressByRns,
watchRns,
}),
[getRnsName, getAddressByRns, watchRns],
);
};

export { useRns };
Loading