From afb90c738a14d6cc55913ebab6981a9d376a33ad Mon Sep 17 00:00:00 2001 From: juliopavila Date: Mon, 25 Sep 2023 22:02:40 -0300 Subject: [PATCH 1/6] Integrate ENS Record Setting in UI --- packages/app/src/App.tsx | 61 +++--- .../src/components/commons/WalletBadge.tsx | 14 +- .../views/publication/components/EnsModal.tsx | 112 ++++++++++++ .../publication/components/SettingSection.tsx | 26 +++ packages/app/src/services/ens.ts | 85 --------- .../src/services/ens/context/ens.context.tsx | 23 +++ .../app/src/services/ens/context/ens.types.ts | 10 + .../app/src/services/ens/context/index.ts | 2 + .../app/src/services/ens/contracts/abi.ts | 11 ++ packages/app/src/services/ens/hooks/useENS.ts | 173 ++++++++++++++++++ .../contexts/publication.context.tsx | 5 +- 11 files changed, 399 insertions(+), 123 deletions(-) create mode 100644 packages/app/src/components/views/publication/components/EnsModal.tsx delete mode 100644 packages/app/src/services/ens.ts create mode 100644 packages/app/src/services/ens/context/ens.context.tsx create mode 100644 packages/app/src/services/ens/context/ens.types.ts create mode 100644 packages/app/src/services/ens/context/index.ts create mode 100644 packages/app/src/services/ens/contracts/abi.ts create mode 100644 packages/app/src/services/ens/hooks/useENS.ts diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 551066e0..61ee945d 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -18,6 +18,7 @@ import { PosterProvider } from "./services/poster/context" import { WalletProvider } from "./connectors/WalletProvider" import { RedirectOldRoute } from "./components/commons/RedicrectOldRoute" import PreviewArticleView from "./components/views/publication/PreviewArticleView" +import { EnsProvider } from "./services/ens/context" const App: React.FC = () => { // the chainId should be from the publication if its present @@ -44,39 +45,41 @@ const App: React.FC = () => { - - - - - - {" "} - } /> - } /> - } /> - } /> - {/* Redirect old routes to new routes */} - } /> - } /> - } /> - } /> - } /> - } /> - {/* New routes */} - - } /> + + + + + + + {" "} + } /> + } /> + } /> + } /> + {/* Redirect old routes to new routes */} + } /> + } /> + } /> + } /> + } /> + } /> + {/* New routes */} + + } /> - } /> + } /> - } /> + } /> - } /> + } /> - } /> - - - - - + } /> + + + + + + diff --git a/packages/app/src/components/commons/WalletBadge.tsx b/packages/app/src/components/commons/WalletBadge.tsx index 19cc0156..6ee75b64 100644 --- a/packages/app/src/components/commons/WalletBadge.tsx +++ b/packages/app/src/components/commons/WalletBadge.tsx @@ -1,11 +1,12 @@ -import React, { useEffect, useState } from "react" +import React, { useEffect } from "react" import { Avatar } from "@mui/material" import * as blockies from "blockies-ts" import { useNotification } from "../../hooks/useNotification" import { useWeb3React } from "@web3-react/core" -import { lookupAddress } from "../../services/ens" +import { useEnsContext } from "../../services/ens/context" +import useENS from "../../services/ens/hooks/useENS" type WalletBadgeProps = { copyable?: boolean @@ -14,10 +15,11 @@ type WalletBadgeProps = { } export const WalletBadge: React.FC = ({ address, hover, copyable }) => { + const { lookupAddress } = useENS() const avatarSrc = blockies.create({ seed: address.toLowerCase() }).toDataURL() const { connector, active, chainId } = useWeb3React() + const { setEnsName } = useEnsContext() - const [ensName, setEnsName] = useState() const openNotification = useNotification() useEffect(() => { @@ -25,14 +27,14 @@ export const WalletBadge: React.FC = ({ address, hover, copyab if (address && active) { const provider = await connector?.getProvider() if (provider != null) { - const ensName = await lookupAddress(provider, address) - setEnsName(ensName ?? undefined) + const ens = await lookupAddress(provider, address) + setEnsName(ens) } } } fetchData().catch(console.error) - }, [active, address, connector, ensName, chainId]) + }, [active, address, connector, chainId, setEnsName, lookupAddress]) const handleAddressClick = async () => { if (copyable) { diff --git a/packages/app/src/components/views/publication/components/EnsModal.tsx b/packages/app/src/components/views/publication/components/EnsModal.tsx new file mode 100644 index 00000000..acdaa64a --- /dev/null +++ b/packages/app/src/components/views/publication/components/EnsModal.tsx @@ -0,0 +1,112 @@ +import { Button, CircularProgress, Grid, Modal, ModalProps, Typography, styled } from "@mui/material" +import React, { useEffect, useRef } from "react" +import { palette, typography } from "../../../../theme" +import { ViewContainer } from "../../../commons/ViewContainer" +import CloseIcon from "@mui/icons-material/Close" +import { useEnsContext } from "../../../../services/ens/context" +import { useWeb3React } from "@web3-react/core" +import useENS from "../../../../services/ens/hooks/useENS" + +interface EnsModalProps extends Omit { + publicationId: string +} + +const ModalContainer = styled(ViewContainer)({ + position: "absolute", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + borderRadius: 8, + width: 648, + background: palette.whites[1000], + padding: 24, +}) + +const EnsModal: React.FC = ({ publicationId, ...props }) => { + const { generateTextRecord, setRecordMulticall, loading, transactionCompleted } = useENS() + const { connector, chainId } = useWeb3React() + const ref = useRef(null) + const { ensName } = useEnsContext() + + useEffect(() => { + if (transactionCompleted) { + props.onClose && props.onClose({}, "backdropClick") + } + }, [props, transactionCompleted]) + + const handleEnsRecord = async () => { + const provider = await connector?.getProvider() + if (provider !== null && ensName && chainId) { + const textData = generateTextRecord(provider, publicationId) + textData && (await setRecordMulticall(provider, textData, chainId)) + } + } + + return ( + { + if (!loading) { + props.onClose && props.onClose({}, "backdropClick") + } + }} + > + + + + + + + Linking Your Publication to Your ENS Name + + + + { + if (!loading) { + props.onClose && props.onClose({}, "escapeKeyDown") + } + }} + /> + + + + + + + + We're registering a record to your ENS name with the publication ID. This allows direct access to your + publication via{" "} + + tabula.gg/#/{ensName} + + . + + + + + + + + + + + ) +} + +export default EnsModal diff --git a/packages/app/src/components/views/publication/components/SettingSection.tsx b/packages/app/src/components/views/publication/components/SettingSection.tsx index 7d709665..0683f931 100644 --- a/packages/app/src/components/views/publication/components/SettingSection.tsx +++ b/packages/app/src/components/views/publication/components/SettingSection.tsx @@ -24,6 +24,8 @@ import { CreatableSelect } from "../../../commons/CreatableSelect" import { CreateSelectOption } from "../../../../models/dropdown" import useLocalStorage from "../../../../hooks/useLocalStorage" import { Pinning, PinningService } from "../../../../models/pinning" +import { useEnsContext } from "../../../../services/ens/context" +import EnsModal from "./EnsModal" type Post = { title: string @@ -45,9 +47,11 @@ const publicationSchema = yup.object().shape({ export const SettingSection: React.FC = ({ couldDelete, couldEdit }) => { const { publicationSlug } = useParams<{ publicationSlug: string }>() const navigate = useNavigate() + const [pinning] = useLocalStorage("pinning", undefined) const [tags, setTags] = useState([]) const [loading, setLoading] = useState(false) + const [openENSModal, setOpenENSModal] = useState(false) const [deleteLoading, setDeleteLoading] = useState(false) const { publication, @@ -57,6 +61,7 @@ export const SettingSection: React.FC = ({ couldDelete, co removePublicationImage, setRemovePublicationImage, } = usePublicationContext() + const { ensName } = useEnsContext() const { executePublication, deletePublication } = usePoster() const { indexing: updateIndexing, @@ -236,6 +241,27 @@ export const SettingSection: React.FC = ({ couldDelete, co errorMsg={tags.length && tags.length >= 6 ? "Add up to 5 tags for your publication" : undefined} /> + {ensName && ( + + + + )} + {ensName && ( + setOpenENSModal(false)} + publicationId={publication?.id ?? ""} + /> + )} {couldDelete && ( diff --git a/packages/app/src/services/ens.ts b/packages/app/src/services/ens.ts deleted file mode 100644 index ad23c35e..00000000 --- a/packages/app/src/services/ens.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { ethers } from "ethers" -import { INFURA_KEY } from "../connectors" - -const ensRegistry = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e" // ENS: Registry with Fallback (singleton same address on different chains) -const ensImplementation = "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85" // ENS: Base Registrar Implementation (singleton same address on different chains) - -const abiPublicResolver = ["function setText(bytes32 node, string calldata key, string calldata value) external"] - -const abiRegistry = [ - "function owner(bytes32 node) external view returns (address)", - "function resolver(bytes32 node) external view returns (address)", -] - -const abiImplementation = ["function ownerOf(uint256 tokenId) public view returns (address owner)"] - -const getTextRecordContentInfura = async (ensName: string, textRecordKey: string) => { - const provider = new ethers.providers.InfuraProvider("mainnet", INFURA_KEY) - const resolver = await provider.getResolver(ensName) - return resolver?.getText(textRecordKey) -} - -export const getTextRecordContent = async ( - ensName: string, - textRecordKey: string, - provider?: ethers.providers.BaseProvider, -) => { - // no connected wallet - if (provider == null) { - return getTextRecordContentInfura(ensName, textRecordKey) - } - - try { - // try to use connected provider - const resolver = await provider.getResolver(ensName) - return resolver?.getText(textRecordKey) - } catch (e) { - // fallback to infura - // we are here if ENS is not supported on the current selected network - return getTextRecordContentInfura(ensName, textRecordKey) - } -} - -export const lookupAddress = async (provider: any, address: string) => { - try { - const web3Provider = new ethers.providers.Web3Provider(provider) - return await web3Provider.lookupAddress(address) - } catch (e) { - console.log("ENS is not supported on this network") - } -} - -/** - * This only works for ENS names using a resolver that conforms to the `abiPublicResolver` (like the PublicResolver). - */ -export const setTextRecord = async ( - provider: ethers.providers.BaseProvider, - ensName: string, - key: string, - content: string, -): Promise => { - const ensRegistryContract = new ethers.Contract(ensRegistry, abiRegistry, provider) - const nameHash = ethers.utils.namehash(ensName) - const ensResolver = await ensRegistryContract.resolver(nameHash) - const ensResolverContract = new ethers.Contract(ensResolver, abiPublicResolver, provider) - return await ensResolverContract.setText(nameHash, key, content) -} - -// the owner of the NFT -export const checkIfIsOwner = async (provider: ethers.providers.Provider, ensName: string, address: string) => { - const BigNumber = ethers.BigNumber - const name = ensName.split(".")[0] // only supports toplevel - const labelHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(name)) - const tokenId = BigNumber.from(labelHash).toString() - const ensImplementationContract = new ethers.Contract(ensImplementation, abiImplementation, provider) - const nftOwner = await ensImplementationContract.ownerOf(tokenId) - return ethers.utils.getAddress(nftOwner) === ethers.utils.getAddress(address) -} - -export const checkIfIsController = async (provider: ethers.providers.Provider, ensName: string, address: string) => { - const ensRegistryContract = new ethers.Contract(ensRegistry, abiRegistry, provider) - const nameHash = ethers.utils.namehash(ensName) - const owner = await ensRegistryContract.owner(nameHash) - - return ethers.utils.getAddress(address) === ethers.utils.getAddress(owner) -} diff --git a/packages/app/src/services/ens/context/ens.context.tsx b/packages/app/src/services/ens/context/ens.context.tsx new file mode 100644 index 00000000..d956103b --- /dev/null +++ b/packages/app/src/services/ens/context/ens.context.tsx @@ -0,0 +1,23 @@ +import { useState } from "react" +import { createGenericContext } from "../../../utils/create-generic-context" +import { EnsContextType, EnsProviderProps } from "./ens.types" + +const [useEnsContext, EnsContextProvider] = createGenericContext() + +const EnsProvider = ({ children }: EnsProviderProps) => { + + const [ensName, setEnsName] = useState(undefined) + + return ( + + {children} + + ) +} + +export { useEnsContext, EnsProvider } diff --git a/packages/app/src/services/ens/context/ens.types.ts b/packages/app/src/services/ens/context/ens.types.ts new file mode 100644 index 00000000..f0ef5504 --- /dev/null +++ b/packages/app/src/services/ens/context/ens.types.ts @@ -0,0 +1,10 @@ +import { ReactNode } from "react" + +export type EnsContextType = { + ensName: string | undefined | null + setEnsName: (value: string | undefined | null) => void +} + +export type EnsProviderProps = { + children: ReactNode +} diff --git a/packages/app/src/services/ens/context/index.ts b/packages/app/src/services/ens/context/index.ts new file mode 100644 index 00000000..ea83870c --- /dev/null +++ b/packages/app/src/services/ens/context/index.ts @@ -0,0 +1,2 @@ +export * from "./ens.context" +export * from "./ens.types" diff --git a/packages/app/src/services/ens/contracts/abi.ts b/packages/app/src/services/ens/contracts/abi.ts new file mode 100644 index 00000000..4512877f --- /dev/null +++ b/packages/app/src/services/ens/contracts/abi.ts @@ -0,0 +1,11 @@ +export const abiPublicResolver = [ + "function setText(bytes32 node, string calldata key, string calldata value) external", + "function multicall(bytes[] calldata data) external", +] + +export const abiRegistry = [ + "function owner(bytes32 node) external view returns (address)", + "function resolver(bytes32 node) external view returns (address)", +] + +export const abiImplementation = ["function ownerOf(uint256 tokenId) public view returns (address owner)"] diff --git a/packages/app/src/services/ens/hooks/useENS.ts b/packages/app/src/services/ens/hooks/useENS.ts new file mode 100644 index 00000000..fd6ba219 --- /dev/null +++ b/packages/app/src/services/ens/hooks/useENS.ts @@ -0,0 +1,173 @@ +import { useState, useCallback } from "react" +import { ethers } from "ethers" +import { SupportedChainId, chainParameters } from "../../../constants/chain" +import { INFURA_KEY } from "../../../connectors" +import { abiImplementation, abiPublicResolver, abiRegistry } from "../contracts/abi" +import { useNotification } from "../../../hooks/useNotification" +import { TransactionReceipt } from "@ethersproject/providers" + +// Addresses obtained from: +// https://discuss.ens.domains/t/namewrapper-updates-including-testnet-deployment-addresses/14505 +const publicResolvers: { [key in SupportedChainId]?: string } = { + [SupportedChainId.SEPOLIA]: "0x8FADE66B79cC9f707aB26799354482EB93a5B7dD", + [SupportedChainId.MAINNET]: "0x231b0ee14048e9dccd1d247744d114a4eb5e8e63", + [SupportedChainId.GOERLI]: "0xd7a4F6473f32aC2Af804B3686AE8F1932bC35750", +} + +const ensRegistry = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e" // ENS: Registry with Fallback (singleton, same address on different chains) +const ensImplementation = "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85" // ENS: Base Registrar Implementation (singleton, same address on different chains) + +export const useENS = () => { + const openNotification = useNotification() + + const [loading, setLoading] = useState(false) + const [transactionCompleted, setTransactionCompleted] = useState(false) + + const getPublicResolverAddress = useCallback((chainId: SupportedChainId): string | undefined => { + return publicResolvers[chainId] + }, []) + + const getTextRecordContentInfura = useCallback(async (ensName: string, textRecordKey: string) => { + const provider = new ethers.providers.InfuraProvider("mainnet", INFURA_KEY) + const resolver = await provider.getResolver(ensName) + return resolver?.getText(textRecordKey) + }, []) + + const getTextRecordContent = useCallback( + async (ensName: string, textRecordKey: string, provider?: ethers.providers.BaseProvider) => { + if (!provider) { + return getTextRecordContentInfura(ensName, textRecordKey) + } + + try { + const resolver = await provider.getResolver(ensName) + return resolver?.getText(textRecordKey) + } catch (e) { + return getTextRecordContentInfura(ensName, textRecordKey) + } + }, + [getTextRecordContentInfura], + ) + + const lookupAddress = useCallback(async (provider: ethers.providers.ExternalProvider, address: string) => { + try { + const web3Provider = new ethers.providers.Web3Provider(provider) + return await web3Provider.lookupAddress(address) + } catch (e) { + console.log("ENS is not supported on this network") + } + }, []) + + const generateTextRecord = useCallback((provider: ethers.providers.ExternalProvider, publicationId: string) => { + try { + const web3Provider = new ethers.providers.Web3Provider(provider) + const contract = new ethers.Contract(ensRegistry, abiPublicResolver, web3Provider) + const node = "0x32a03d3aa475a2eac9dddc2da7fb8e45544e77a3e5657aa40fdf6b506f9ff896" // Default Data Node + return contract.interface.encodeFunctionData("setText", [node, "tabula", publicationId]) + } catch (e) { + console.log("ENS is not supported on this network") + } + }, []) + + const setRecordMulticall = useCallback( + async (provider: ethers.providers.ExternalProvider, textRecord: string, chainId: SupportedChainId) => { + const parameters = chainParameters(chainId) + const URL = parameters ? parameters.blockExplorerUrls[0] : "https://goerli.etherscan.io/tx/" + setLoading(true) + const publicResolver = getPublicResolverAddress(chainId) + if (!publicResolver) { + openNotification({ + message: "Public resolver not found for the selected chain.", + variant: "error", + }) + setLoading(false) + return + } + + try { + const web3Provider = new ethers.providers.Web3Provider(provider) + const contract = new ethers.Contract(ensRegistry, abiPublicResolver, web3Provider) + const signer = web3Provider.getSigner() + const data = contract.interface.encodeFunctionData("multicall", [[textRecord]]) + + if (!data) { + openNotification({ + message: "Failed to encode data for multicall.", + variant: "error", + }) + setLoading(false) + return + } + + const tx = await signer.sendTransaction({ + to: publicResolver, + data: data, + }) + const receipt: TransactionReceipt = await tx.wait() + + openNotification({ + message: "Transaction completed successfully!", + variant: "success", + detailsLink: `${URL}tx/${receipt.transactionHash}`, + }) + setTransactionCompleted(true) + } catch (e) { + console.log("error", e) + setLoading(false) + + openNotification({ + message: "ENS is not supported on this network or an error occurred.", + variant: "error", + }) + } finally { + setLoading(false) + } + }, + [getPublicResolverAddress, openNotification], + ) + + const setTextRecord = useCallback( + async (provider: ethers.providers.BaseProvider, ensName: string, key: string, content: string) => { + const ensRegistryContract = new ethers.Contract(ensRegistry, abiRegistry, provider) + const nameHash = ethers.utils.namehash(ensName) + const ensResolver = await ensRegistryContract.resolver(nameHash) + const ensResolverContract = new ethers.Contract(ensResolver, abiPublicResolver, provider) + return ensResolverContract.setText(nameHash, key, content) + }, + [], + ) + + const checkIfIsOwner = useCallback(async (provider: ethers.providers.Provider, ensName: string, address: string) => { + const BigNumber = ethers.BigNumber + const name = ensName.split(".")[0] + const labelHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(name)) + const tokenId = BigNumber.from(labelHash).toString() + const ensImplementationContract = new ethers.Contract(ensImplementation, abiImplementation, provider) + const nftOwner = await ensImplementationContract.ownerOf(tokenId) + return ethers.utils.getAddress(nftOwner) === ethers.utils.getAddress(address) + }, []) + + const checkIfIsController = useCallback( + async (provider: ethers.providers.Provider, ensName: string, address: string) => { + const ensRegistryContract = new ethers.Contract(ensRegistry, abiRegistry, provider) + const nameHash = ethers.utils.namehash(ensName) + const owner = await ensRegistryContract.owner(nameHash) + return ethers.utils.getAddress(address) === ethers.utils.getAddress(owner) + }, + [], + ) + + return { + getTextRecordContent, + lookupAddress, + generateTextRecord, + setRecordMulticall, + checkIfIsController, + checkIfIsOwner, + setTextRecord, + loading, + transactionCompleted, + } +} + +export default useENS diff --git a/packages/app/src/services/publications/contexts/publication.context.tsx b/packages/app/src/services/publications/contexts/publication.context.tsx index d0ccd40c..6a78e91b 100644 --- a/packages/app/src/services/publications/contexts/publication.context.tsx +++ b/packages/app/src/services/publications/contexts/publication.context.tsx @@ -2,13 +2,13 @@ import { ethers } from "ethers" import { useState } from "react" import { Permission, Publication } from "../../../models/publication" import { createGenericContext } from "../../../utils/create-generic-context" -import { getTextRecordContent } from "../../ens" import { PublicationContextType, PublicationProviderProps } from "./publication.types" - +import useENS from "../../ens/hooks/useENS" const [usePublicationContext, PublicationContextProvider] = createGenericContext() const PublicationProvider = ({ children }: PublicationProviderProps) => { + const { getTextRecordContent } = useENS() const [currentPath, setCurrentPath] = useState(undefined) const [publications, setPublications] = useState(undefined) const [publication, setPublication] = useState(undefined) @@ -17,7 +17,6 @@ const PublicationProvider = ({ children }: PublicationProviderProps) => { const [draftPublicationImage, setDraftPublicationImage] = useState(undefined) const [loading, setLoading] = useState(false) const [ipfsLoading, setIpfsLoading] = useState(false) - const [slugToPublicationId, setSlugToPublicationId] = useState<{ [key: string]: string }>({}) const [publicationAvatar, setPublicationAvatar] = useState<{ publicationId: string; uri: string } | undefined>( undefined, From 8117e823f3a3dec31f00afd44d570f7e2d71ac62 Mon Sep 17 00:00:00 2001 From: juliopavila Date: Wed, 27 Sep 2023 13:15:46 -0300 Subject: [PATCH 2/6] feat: add ens subgraph to fetch the ens name list --- packages/app/.env.example | 2 + .../views/publication/components/EnsModal.tsx | 24 +++++++--- .../publication/components/SettingSection.tsx | 23 +++++++-- .../src/services/ens/context/ens.context.tsx | 7 ++- .../app/src/services/ens/context/ens.types.ts | 3 ++ packages/app/src/services/ens/hooks/useENS.ts | 47 +++++++++++++++---- packages/app/src/services/ens/queries.ts | 23 +++++++++ packages/app/src/services/graphql.ts | 28 +++++++++++ 8 files changed, 135 insertions(+), 22 deletions(-) create mode 100644 packages/app/src/services/ens/queries.ts diff --git a/packages/app/.env.example b/packages/app/.env.example index 506b3d57..651f50cc 100644 --- a/packages/app/.env.example +++ b/packages/app/.env.example @@ -8,6 +8,8 @@ REACT_APP_SUBGRAPH_SEPOLIA=auryn-macmillan/tabula-sepolia REACT_APP_SUBGRAPH_POLYGON=auryn-macmillan/tabula-polygon REACT_APP_SUBGRAPH_ARBITRUM=auryn-macmillan/tabula-arbitrum REACT_APP_SUBGRAPH_OPTIMISM=auryn-macmillan/tabula-optimism +REACT_APP_ENS_SUBGRAPH_MAINNET=ensdomains/ens +REACT_APP_ENS_SUBGRAPH_GOERLI=ensdomains/ensgoerli REACT_APP_IPFS_GATEWAY=https://ipfs.io/ipfs REACT_APP_SUBGRAPH_OPTIMISM_ON_GNOSIS_CHAIN=auryn-macmillan/tabula-optimism-on-gnosis-chain REACT_APP_INFURA_API_KEY= diff --git a/packages/app/src/components/views/publication/components/EnsModal.tsx b/packages/app/src/components/views/publication/components/EnsModal.tsx index acdaa64a..b101e6ef 100644 --- a/packages/app/src/components/views/publication/components/EnsModal.tsx +++ b/packages/app/src/components/views/publication/components/EnsModal.tsx @@ -1,11 +1,12 @@ import { Button, CircularProgress, Grid, Modal, ModalProps, Typography, styled } from "@mui/material" -import React, { useEffect, useRef } from "react" +import React, { useEffect, useRef, useState } from "react" import { palette, typography } from "../../../../theme" import { ViewContainer } from "../../../commons/ViewContainer" import CloseIcon from "@mui/icons-material/Close" import { useEnsContext } from "../../../../services/ens/context" import { useWeb3React } from "@web3-react/core" import useENS from "../../../../services/ens/hooks/useENS" +import { Dropdown } from "../../../commons/Dropdown" interface EnsModalProps extends Omit { publicationId: string @@ -26,7 +27,8 @@ const EnsModal: React.FC = ({ publicationId, ...props }) => { const { generateTextRecord, setRecordMulticall, loading, transactionCompleted } = useENS() const { connector, chainId } = useWeb3React() const ref = useRef(null) - const { ensName } = useEnsContext() + const { ensNameList } = useEnsContext() + const [ensNameSelected, setEnsNameSelected] = useState("") useEffect(() => { if (transactionCompleted) { @@ -36,8 +38,8 @@ const EnsModal: React.FC = ({ publicationId, ...props }) => { const handleEnsRecord = async () => { const provider = await connector?.getProvider() - if (provider !== null && ensName && chainId) { - const textData = generateTextRecord(provider, publicationId) + if (provider !== null && ensNameSelected && chainId) { + const textData = generateTextRecord(provider, publicationId, ensNameSelected) textData && (await setRecordMulticall(provider, textData, chainId)) } } @@ -78,6 +80,16 @@ const EnsModal: React.FC = ({ publicationId, ...props }) => { + + { + setEnsNameSelected(e.value) + }} + /> + + @@ -90,7 +102,7 @@ const EnsModal: React.FC = ({ publicationId, ...props }) => { cursor: "pointer", }} > - tabula.gg/#/{ensName} + tabula.gg/#/{ensNameSelected ?? "yourENS"} . @@ -98,7 +110,7 @@ const EnsModal: React.FC = ({ publicationId, ...props }) => { - diff --git a/packages/app/src/components/views/publication/components/SettingSection.tsx b/packages/app/src/components/views/publication/components/SettingSection.tsx index 0683f931..f2eac736 100644 --- a/packages/app/src/components/views/publication/components/SettingSection.tsx +++ b/packages/app/src/components/views/publication/components/SettingSection.tsx @@ -26,6 +26,7 @@ import useLocalStorage from "../../../../hooks/useLocalStorage" import { Pinning, PinningService } from "../../../../models/pinning" import { useEnsContext } from "../../../../services/ens/context" import EnsModal from "./EnsModal" +import useENS from "../../../../services/ens/hooks/useENS" type Post = { title: string @@ -47,7 +48,6 @@ const publicationSchema = yup.object().shape({ export const SettingSection: React.FC = ({ couldDelete, couldEdit }) => { const { publicationSlug } = useParams<{ publicationSlug: string }>() const navigate = useNavigate() - const [pinning] = useLocalStorage("pinning", undefined) const [tags, setTags] = useState([]) const [loading, setLoading] = useState(false) @@ -61,7 +61,8 @@ export const SettingSection: React.FC = ({ couldDelete, co removePublicationImage, setRemovePublicationImage, } = usePublicationContext() - const { ensName } = useEnsContext() + const { ensNameList } = useEnsContext() + const { fetchNames } = useENS() const { executePublication, deletePublication } = usePoster() const { indexing: updateIndexing, @@ -80,6 +81,11 @@ export const SettingSection: React.FC = ({ couldDelete, co resolver: yupResolver(publicationSchema), }) + useEffect(() => { + fetchNames() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + useEffect(() => { saveIsEditingPublication(true) // returned function will be called on component unmount @@ -98,6 +104,15 @@ export const SettingSection: React.FC = ({ couldDelete, co } }, [loading, publication, setCurrentTimestamp, setValue]) + useEffect(() => { + if (publication && !loading && publication.lastUpdated) { + setValue("title", publication.title) + setValue("description", publication.description || "") + setTags(publication.tags || []) + setCurrentTimestamp(parseInt(publication.lastUpdated)) + } + }, [loading, publication, setCurrentTimestamp, setValue]) + useEffect(() => { if (redirect) { navigate("../publications") @@ -241,7 +256,7 @@ export const SettingSection: React.FC = ({ couldDelete, co errorMsg={tags.length && tags.length >= 6 ? "Add up to 5 tags for your publication" : undefined} /> - {ensName && ( + {ensNameList && ( )} - {ensName && ( + {ensNameList && ( setOpenENSModal(false)} diff --git a/packages/app/src/services/ens/context/ens.context.tsx b/packages/app/src/services/ens/context/ens.context.tsx index d956103b..58eff157 100644 --- a/packages/app/src/services/ens/context/ens.context.tsx +++ b/packages/app/src/services/ens/context/ens.context.tsx @@ -1,18 +1,21 @@ import { useState } from "react" import { createGenericContext } from "../../../utils/create-generic-context" import { EnsContextType, EnsProviderProps } from "./ens.types" +import { DropdownOption } from "../../../models/dropdown" const [useEnsContext, EnsContextProvider] = createGenericContext() const EnsProvider = ({ children }: EnsProviderProps) => { - const [ensName, setEnsName] = useState(undefined) + const [ensNameList, setEnsNameList] = useState([]) return ( {children} diff --git a/packages/app/src/services/ens/context/ens.types.ts b/packages/app/src/services/ens/context/ens.types.ts index f0ef5504..fed55bed 100644 --- a/packages/app/src/services/ens/context/ens.types.ts +++ b/packages/app/src/services/ens/context/ens.types.ts @@ -1,8 +1,11 @@ import { ReactNode } from "react" +import { DropdownOption } from "../../../models/dropdown" export type EnsContextType = { ensName: string | undefined | null setEnsName: (value: string | undefined | null) => void + ensNameList: DropdownOption[] + setEnsNameList: (value: DropdownOption[]) => void } export type EnsProviderProps = { diff --git a/packages/app/src/services/ens/hooks/useENS.ts b/packages/app/src/services/ens/hooks/useENS.ts index fd6ba219..c91d77db 100644 --- a/packages/app/src/services/ens/hooks/useENS.ts +++ b/packages/app/src/services/ens/hooks/useENS.ts @@ -5,6 +5,11 @@ import { INFURA_KEY } from "../../../connectors" import { abiImplementation, abiPublicResolver, abiRegistry } from "../contracts/abi" import { useNotification } from "../../../hooks/useNotification" import { TransactionReceipt } from "@ethersproject/providers" +import { GET_ENS_NAMES_QUERY } from "../queries" +import { ensSubgraphClient } from "../../graphql" +import { useWeb3React } from "@web3-react/core" +import { DropdownOption } from "../../../models/dropdown" +import { useEnsContext } from "../context" // Addresses obtained from: // https://discuss.ens.domains/t/namewrapper-updates-including-testnet-deployment-addresses/14505 @@ -19,10 +24,28 @@ const ensImplementation = "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85" // ENS: B export const useENS = () => { const openNotification = useNotification() + const { chainId, account } = useWeb3React() + const { setEnsNameList } = useEnsContext() + const client = ensSubgraphClient(chainId) const [loading, setLoading] = useState(false) const [transactionCompleted, setTransactionCompleted] = useState(false) + const fetchNames = async () => { + client + .query(GET_ENS_NAMES_QUERY, { id: account?.toLowerCase() }) + .toPromise() + .then((result) => { + const data = result.data + if (data.account && data.account.wrappedDomains.length) { + const list: DropdownOption[] = data.account.wrappedDomains.map((ens: { domain: { name: string } }) => { + return { label: ens.domain.name, value: ens.domain.name } + }) + setEnsNameList(list) + } + }) + } + const getPublicResolverAddress = useCallback((chainId: SupportedChainId): string | undefined => { return publicResolvers[chainId] }, []) @@ -58,16 +81,19 @@ export const useENS = () => { } }, []) - const generateTextRecord = useCallback((provider: ethers.providers.ExternalProvider, publicationId: string) => { - try { - const web3Provider = new ethers.providers.Web3Provider(provider) - const contract = new ethers.Contract(ensRegistry, abiPublicResolver, web3Provider) - const node = "0x32a03d3aa475a2eac9dddc2da7fb8e45544e77a3e5657aa40fdf6b506f9ff896" // Default Data Node - return contract.interface.encodeFunctionData("setText", [node, "tabula", publicationId]) - } catch (e) { - console.log("ENS is not supported on this network") - } - }, []) + const generateTextRecord = useCallback( + (provider: ethers.providers.ExternalProvider, publicationId: string, ensName: string) => { + try { + const web3Provider = new ethers.providers.Web3Provider(provider) + const contract = new ethers.Contract(ensRegistry, abiPublicResolver, web3Provider) + const namehash = ethers.utils.namehash(ensName) // Calculate namehash of the ENS name + return contract.interface.encodeFunctionData("setText", [namehash, "tabula", publicationId]) + } catch (e) { + console.log("ENS is not supported on this network") + } + }, + [], + ) const setRecordMulticall = useCallback( async (provider: ethers.providers.ExternalProvider, textRecord: string, chainId: SupportedChainId) => { @@ -165,6 +191,7 @@ export const useENS = () => { checkIfIsController, checkIfIsOwner, setTextRecord, + fetchNames, loading, transactionCompleted, } diff --git a/packages/app/src/services/ens/queries.ts b/packages/app/src/services/ens/queries.ts new file mode 100644 index 00000000..80391377 --- /dev/null +++ b/packages/app/src/services/ens/queries.ts @@ -0,0 +1,23 @@ +import { gql } from "urql" + +export const GET_ENS_NAMES_QUERY = gql` + query getNames($id: String!) { + account(id: $id) { + wrappedDomains(first: 1000) { + expiryDate + fuses + domain { + id + labelName + labelhash + name + isMigrated + parent { + name + id + } + } + } + } + } +` diff --git a/packages/app/src/services/graphql.ts b/packages/app/src/services/graphql.ts index 6980372c..eaacb89c 100644 --- a/packages/app/src/services/graphql.ts +++ b/packages/app/src/services/graphql.ts @@ -28,6 +28,12 @@ if (!process.env.REACT_APP_SUBGRAPH_OPTIMISM) { if (!process.env.REACT_APP_SUBGRAPH_OPTIMISM_ON_GNOSIS_CHAIN) { throw new Error("REACT_APP_SUBGRAPH_OPTIMISM_ON_GNOSIS_CHAIN is not set") } +if (!process.env.REACT_APP_ENS_SUBGRAPH_MAINNET) { + throw new Error("REACT_APP_ENS_SUBGRAPH_MAINNET is not set") +} +if (!process.env.REACT_APP_ENS_SUBGRAPH_GOERLI) { + throw new Error("REACT_APP_ENS_SUBGRAPH_GOERLI is not set") +} const BASE_SUBGRAPH_URL = process.env.REACT_APP_SUBGRAPH_BASE_URL const SUBGRAPH_GNOSIS_CHAIN = process.env.REACT_APP_SUBGRAPH_GNOSIS_CHAIN @@ -38,6 +44,8 @@ const SUBGRAPH_POLYGON = process.env.REACT_APP_SUBGRAPH_POLYGON const SUBGRAPH_ARBITRUM = process.env.REACT_APP_SUBGRAPH_ARBITRUM const SUBGRAPH_OPTIMISM = process.env.REACT_APP_SUBGRAPH_OPTIMISM const SUBGRAPH_OPTIMISM_ON_GNOSIS_CHAIN = process.env.REACT_APP_SUBGRAPH_OPTIMISM_ON_GNOSIS_CHAIN +const ENS_SUBGRAPH_MAINNET = process.env.REACT_APP_ENS_SUBGRAPH_MAINNET +const ENS_SUBGRAPH_GOERLI = process.env.REACT_APP_ENS_SUBGRAPH_GOERLI const getUrl = (chainId?: number) => { switch (chainId) { @@ -62,6 +70,17 @@ const getUrl = (chainId?: number) => { } } +const getENSUrl = (chainId?: number) => { + switch (chainId) { + case SupportedChainId.MAINNET: + return BASE_SUBGRAPH_URL + ENS_SUBGRAPH_MAINNET + case SupportedChainId.GOERLI: + return BASE_SUBGRAPH_URL + ENS_SUBGRAPH_GOERLI + default: + return BASE_SUBGRAPH_URL + ENS_SUBGRAPH_GOERLI + } +} + export const subgraphClient = (chainId?: number) => createClient({ url: getUrl(chainId), @@ -70,3 +89,12 @@ export const subgraphClient = (chainId?: number) => cache: "no-cache", }, }) + +export const ensSubgraphClient = (chainId?: number) => + createClient({ + url: getENSUrl(chainId), + exchanges: [...defaultExchanges], + fetchOptions: { + cache: "no-cache", + }, + }) From 931883b08c9923ee4d92123f20608bcc5db9854d Mon Sep 17 00:00:00 2001 From: juliopavila Date: Fri, 6 Oct 2023 18:03:18 -0300 Subject: [PATCH 3/6] feat: Use mainnet ENS names for all chains --- packages/app/src/services/graphql.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/app/src/services/graphql.ts b/packages/app/src/services/graphql.ts index eaacb89c..f93b0e5a 100644 --- a/packages/app/src/services/graphql.ts +++ b/packages/app/src/services/graphql.ts @@ -73,6 +73,11 @@ const getUrl = (chainId?: number) => { const getENSUrl = (chainId?: number) => { switch (chainId) { case SupportedChainId.MAINNET: + case SupportedChainId.GNOSIS_CHAIN: + case SupportedChainId.POLYGON: + case SupportedChainId.OPTIMISM: + case SupportedChainId.OPTIMISM_ON_GNOSIS_CHAIN: + case SupportedChainId.ARBITRUM: return BASE_SUBGRAPH_URL + ENS_SUBGRAPH_MAINNET case SupportedChainId.GOERLI: return BASE_SUBGRAPH_URL + ENS_SUBGRAPH_GOERLI From 0b0887ac8d1d4f1e470a57c8c741c41668481d9c Mon Sep 17 00:00:00 2001 From: juliopavila Date: Sun, 8 Oct 2023 19:07:19 -0300 Subject: [PATCH 4/6] chore: use setText instead multicall --- .../views/publication/components/EnsModal.tsx | 5 +-- packages/app/src/services/ens/hooks/useENS.ts | 44 +++++-------------- packages/app/src/services/graphql.ts | 2 +- 3 files changed, 13 insertions(+), 38 deletions(-) diff --git a/packages/app/src/components/views/publication/components/EnsModal.tsx b/packages/app/src/components/views/publication/components/EnsModal.tsx index b101e6ef..aa0b7b65 100644 --- a/packages/app/src/components/views/publication/components/EnsModal.tsx +++ b/packages/app/src/components/views/publication/components/EnsModal.tsx @@ -24,7 +24,7 @@ const ModalContainer = styled(ViewContainer)({ }) const EnsModal: React.FC = ({ publicationId, ...props }) => { - const { generateTextRecord, setRecordMulticall, loading, transactionCompleted } = useENS() + const { setTextRecord, loading, transactionCompleted } = useENS() const { connector, chainId } = useWeb3React() const ref = useRef(null) const { ensNameList } = useEnsContext() @@ -39,8 +39,7 @@ const EnsModal: React.FC = ({ publicationId, ...props }) => { const handleEnsRecord = async () => { const provider = await connector?.getProvider() if (provider !== null && ensNameSelected && chainId) { - const textData = generateTextRecord(provider, publicationId, ensNameSelected) - textData && (await setRecordMulticall(provider, textData, chainId)) + await setTextRecord(provider, publicationId, ensNameSelected, chainId) } } diff --git a/packages/app/src/services/ens/hooks/useENS.ts b/packages/app/src/services/ens/hooks/useENS.ts index c91d77db..dfdc997b 100644 --- a/packages/app/src/services/ens/hooks/useENS.ts +++ b/packages/app/src/services/ens/hooks/useENS.ts @@ -81,22 +81,13 @@ export const useENS = () => { } }, []) - const generateTextRecord = useCallback( - (provider: ethers.providers.ExternalProvider, publicationId: string, ensName: string) => { - try { - const web3Provider = new ethers.providers.Web3Provider(provider) - const contract = new ethers.Contract(ensRegistry, abiPublicResolver, web3Provider) - const namehash = ethers.utils.namehash(ensName) // Calculate namehash of the ENS name - return contract.interface.encodeFunctionData("setText", [namehash, "tabula", publicationId]) - } catch (e) { - console.log("ENS is not supported on this network") - } - }, - [], - ) - - const setRecordMulticall = useCallback( - async (provider: ethers.providers.ExternalProvider, textRecord: string, chainId: SupportedChainId) => { + const setTextRecord = useCallback( + async ( + provider: ethers.providers.ExternalProvider, + publicationId: string, + ensName: string, + chainId: SupportedChainId, + ) => { const parameters = chainParameters(chainId) const URL = parameters ? parameters.blockExplorerUrls[0] : "https://goerli.etherscan.io/tx/" setLoading(true) @@ -109,22 +100,20 @@ export const useENS = () => { setLoading(false) return } - try { const web3Provider = new ethers.providers.Web3Provider(provider) const contract = new ethers.Contract(ensRegistry, abiPublicResolver, web3Provider) + const namehash = ethers.utils.namehash(ensName) // Calculate namehash of the ENS name const signer = web3Provider.getSigner() - const data = contract.interface.encodeFunctionData("multicall", [[textRecord]]) - + const data = contract.interface.encodeFunctionData("setText", [namehash, "tabula", publicationId]) if (!data) { openNotification({ - message: "Failed to encode data for multicall.", + message: "Failed to encode data for setText.", variant: "error", }) setLoading(false) return } - const tx = await signer.sendTransaction({ to: publicResolver, data: data, @@ -152,17 +141,6 @@ export const useENS = () => { [getPublicResolverAddress, openNotification], ) - const setTextRecord = useCallback( - async (provider: ethers.providers.BaseProvider, ensName: string, key: string, content: string) => { - const ensRegistryContract = new ethers.Contract(ensRegistry, abiRegistry, provider) - const nameHash = ethers.utils.namehash(ensName) - const ensResolver = await ensRegistryContract.resolver(nameHash) - const ensResolverContract = new ethers.Contract(ensResolver, abiPublicResolver, provider) - return ensResolverContract.setText(nameHash, key, content) - }, - [], - ) - const checkIfIsOwner = useCallback(async (provider: ethers.providers.Provider, ensName: string, address: string) => { const BigNumber = ethers.BigNumber const name = ensName.split(".")[0] @@ -186,8 +164,6 @@ export const useENS = () => { return { getTextRecordContent, lookupAddress, - generateTextRecord, - setRecordMulticall, checkIfIsController, checkIfIsOwner, setTextRecord, diff --git a/packages/app/src/services/graphql.ts b/packages/app/src/services/graphql.ts index f93b0e5a..34ae7423 100644 --- a/packages/app/src/services/graphql.ts +++ b/packages/app/src/services/graphql.ts @@ -82,7 +82,7 @@ const getENSUrl = (chainId?: number) => { case SupportedChainId.GOERLI: return BASE_SUBGRAPH_URL + ENS_SUBGRAPH_GOERLI default: - return BASE_SUBGRAPH_URL + ENS_SUBGRAPH_GOERLI + return BASE_SUBGRAPH_URL + ENS_SUBGRAPH_MAINNET } } From f1d061603c564e37ef6bda7abccf746b32490645 Mon Sep 17 00:00:00 2001 From: juliopavila Date: Mon, 9 Oct 2023 16:25:30 -0300 Subject: [PATCH 5/6] feat: add switch network modal --- .../src/components/commons/NetworkModal.tsx | 111 ++++++++++++++++++ .../publication/components/SettingSection.tsx | 17 ++- 2 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 packages/app/src/components/commons/NetworkModal.tsx diff --git a/packages/app/src/components/commons/NetworkModal.tsx b/packages/app/src/components/commons/NetworkModal.tsx new file mode 100644 index 00000000..6a625cb3 --- /dev/null +++ b/packages/app/src/components/commons/NetworkModal.tsx @@ -0,0 +1,111 @@ +import { Button, CircularProgress, Grid, Modal, Typography, styled } from "@mui/material" +import React, { useRef, useState } from "react" +import { palette, typography } from "../../theme" +import { ViewContainer } from "./ViewContainer" +import CloseIcon from "@mui/icons-material/Close" +import { useWeb3React } from "@web3-react/core" +import { useNotification } from "../../hooks/useNotification" + +type NetworkError = { + code: number + message: string + stack: string +} + +const ModalContainer = styled(ViewContainer)({ + position: "absolute", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + borderRadius: 8, + width: 648, + background: palette.whites[1000], + padding: 24, +}) + +const NetworkModal: React.FC = ({ open, handleClose }) => { + const ref = useRef(null) + const openNotification = useNotification() + const { library } = useWeb3React() + const [loading, setLoading] = useState(false) + + const handleSwitch = async () => { + if (library) { + try { + setLoading(true) + await library.send("wallet_switchEthereumChain", [{ chainId: "0x1" }]) + handleClose({}, "escapeKeyDown") + } catch (switchError: unknown) { + const error = switchError as NetworkError + setLoading(false) + openNotification({ + message: error.message, + variant: "error", + }) + if (error && error.code === 4902) { + setLoading(false) + try { + await library.send("wallet_addEthereumChain", [ + { + chainId: "0x1", + }, + ]) + } catch (addError) { + console.error(addError) + setLoading(false) + } + } + } + } + } + return ( + + + + + + + + Network Switch Required + + + + { + !loading && handleClose({}, "escapeKeyDown") + }} + /> + + + + + + + +

+ You are currently connected to a network other than mainnet. To proceed with transactions involving + ENS domains, please switch to mainnet. +

+
+
+
+ + + + +
+
+
+ ) +} + +export default NetworkModal diff --git a/packages/app/src/components/views/publication/components/SettingSection.tsx b/packages/app/src/components/views/publication/components/SettingSection.tsx index f2eac736..ebdc50f2 100644 --- a/packages/app/src/components/views/publication/components/SettingSection.tsx +++ b/packages/app/src/components/views/publication/components/SettingSection.tsx @@ -27,6 +27,9 @@ import { Pinning, PinningService } from "../../../../models/pinning" import { useEnsContext } from "../../../../services/ens/context" import EnsModal from "./EnsModal" import useENS from "../../../../services/ens/hooks/useENS" +import { useWeb3React } from "@web3-react/core" +import NetworkModal from "../../../commons/NetworkModal" +import { SupportedChainId } from "../../../../constants/chain" type Post = { title: string @@ -48,10 +51,12 @@ const publicationSchema = yup.object().shape({ export const SettingSection: React.FC = ({ couldDelete, couldEdit }) => { const { publicationSlug } = useParams<{ publicationSlug: string }>() const navigate = useNavigate() + const { chainId } = useWeb3React() const [pinning] = useLocalStorage("pinning", undefined) const [tags, setTags] = useState([]) const [loading, setLoading] = useState(false) const [openENSModal, setOpenENSModal] = useState(false) + const [openNetworkModal, setOpenNetworkModal] = useState(false) const [deleteLoading, setDeleteLoading] = useState(false) const { publication, @@ -193,6 +198,15 @@ export const SettingSection: React.FC = ({ couldDelete, co } } + const handleEns = () => { + if (chainId) { + if ([SupportedChainId.MAINNET, SupportedChainId.GOERLI, SupportedChainId.SEPOLIA].includes(chainId)) { + return setOpenENSModal(true) + } + return setOpenNetworkModal(true) + } + } + return ( @@ -259,7 +273,7 @@ export const SettingSection: React.FC = ({ couldDelete, co {ensNameList && ( )} + setOpenNetworkModal(false)} /> {ensNameList && ( Date: Wed, 11 Oct 2023 11:05:27 -0300 Subject: [PATCH 6/6] chore: add network modal types --- packages/app/src/components/commons/NetworkModal.tsx | 11 ++++++----- .../views/publication/components/SettingSection.tsx | 3 +-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/app/src/components/commons/NetworkModal.tsx b/packages/app/src/components/commons/NetworkModal.tsx index 6a625cb3..66612f80 100644 --- a/packages/app/src/components/commons/NetworkModal.tsx +++ b/packages/app/src/components/commons/NetworkModal.tsx @@ -1,4 +1,4 @@ -import { Button, CircularProgress, Grid, Modal, Typography, styled } from "@mui/material" +import { Button, CircularProgress, Grid, Modal, ModalProps, Typography, styled } from "@mui/material" import React, { useRef, useState } from "react" import { palette, typography } from "../../theme" import { ViewContainer } from "./ViewContainer" @@ -12,6 +12,7 @@ type NetworkError = { stack: string } +interface NetworkModalProps extends Omit {} const ModalContainer = styled(ViewContainer)({ position: "absolute", top: "50%", @@ -23,7 +24,7 @@ const ModalContainer = styled(ViewContainer)({ padding: 24, }) -const NetworkModal: React.FC = ({ open, handleClose }) => { +const NetworkModal: React.FC = ({ open, onClose }) => { const ref = useRef(null) const openNotification = useNotification() const { library } = useWeb3React() @@ -34,7 +35,7 @@ const NetworkModal: React.FC = ({ open, handleClose }) => { try { setLoading(true) await library.send("wallet_switchEthereumChain", [{ chainId: "0x1" }]) - handleClose({}, "escapeKeyDown") + onClose && onClose({}, "escapeKeyDown") } catch (switchError: unknown) { const error = switchError as NetworkError setLoading(false) @@ -59,7 +60,7 @@ const NetworkModal: React.FC = ({ open, handleClose }) => { } } return ( - + @@ -78,7 +79,7 @@ const NetworkModal: React.FC = ({ open, handleClose }) => { { - !loading && handleClose({}, "escapeKeyDown") + !loading && onClose && onClose({}, "escapeKeyDown") }} /> diff --git a/packages/app/src/components/views/publication/components/SettingSection.tsx b/packages/app/src/components/views/publication/components/SettingSection.tsx index ebdc50f2..0db54ca5 100644 --- a/packages/app/src/components/views/publication/components/SettingSection.tsx +++ b/packages/app/src/components/views/publication/components/SettingSection.tsx @@ -7,7 +7,6 @@ import { Grid, InputLabel, TextField, - // Typography, } from "@mui/material" import React, { useEffect, useState } from "react" import { useForm, Controller } from "react-hook-form" @@ -284,7 +283,7 @@ export const SettingSection: React.FC = ({ couldDelete, co )} - setOpenNetworkModal(false)} /> + setOpenNetworkModal(false)} /> {ensNameList && (