Skip to content

Commit

Permalink
feat: add ens subgraph to fetch the ens name list
Browse files Browse the repository at this point in the history
  • Loading branch information
juliopavila committed Sep 27, 2023
1 parent afb90c7 commit 8117e82
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 22 deletions.
2 changes: 2 additions & 0 deletions packages/app/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ModalProps, "children"> {
publicationId: string
Expand All @@ -26,7 +27,8 @@ const EnsModal: React.FC<EnsModalProps> = ({ 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<string>("")

useEffect(() => {
if (transactionCompleted) {
Expand All @@ -36,8 +38,8 @@ const EnsModal: React.FC<EnsModalProps> = ({ 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))
}
}
Expand Down Expand Up @@ -78,6 +80,16 @@ const EnsModal: React.FC<EnsModalProps> = ({ publicationId, ...props }) => {
</Grid>
</Grid>

<Grid item>
<Dropdown
title="Select an ENS name"
options={ensNameList}
onSelected={(e) => {
setEnsNameSelected(e.value)
}}
/>
</Grid>

<Grid item>
<Grid container gap={1}>
<Typography fontFamily={typography.fontFamilies.sans}>
Expand All @@ -90,15 +102,15 @@ const EnsModal: React.FC<EnsModalProps> = ({ publicationId, ...props }) => {
cursor: "pointer",
}}
>
tabula.gg/#/{ensName}
tabula.gg/#/{ensNameSelected ?? "yourENS"}
</span>
.
</Typography>
</Grid>
</Grid>

<Grid item>
<Button variant="contained" type="submit" onClick={handleEnsRecord} disabled={loading}>
<Button variant="contained" type="submit" onClick={handleEnsRecord} disabled={loading || !ensNameSelected}>
{loading && <CircularProgress size={20} sx={{ marginRight: 1 }} />}
Continue
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -47,7 +48,6 @@ const publicationSchema = yup.object().shape({
export const SettingSection: React.FC<SettingsSectionProps> = ({ couldDelete, couldEdit }) => {
const { publicationSlug } = useParams<{ publicationSlug: string }>()
const navigate = useNavigate()

const [pinning] = useLocalStorage<Pinning | undefined>("pinning", undefined)
const [tags, setTags] = useState<string[]>([])
const [loading, setLoading] = useState<boolean>(false)
Expand All @@ -61,7 +61,8 @@ export const SettingSection: React.FC<SettingsSectionProps> = ({ couldDelete, co
removePublicationImage,
setRemovePublicationImage,
} = usePublicationContext()
const { ensName } = useEnsContext()
const { ensNameList } = useEnsContext()
const { fetchNames } = useENS()
const { executePublication, deletePublication } = usePoster()
const {
indexing: updateIndexing,
Expand All @@ -80,6 +81,11 @@ export const SettingSection: React.FC<SettingsSectionProps> = ({ 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
Expand All @@ -98,6 +104,15 @@ export const SettingSection: React.FC<SettingsSectionProps> = ({ 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")
Expand Down Expand Up @@ -241,7 +256,7 @@ export const SettingSection: React.FC<SettingsSectionProps> = ({ couldDelete, co
errorMsg={tags.length && tags.length >= 6 ? "Add up to 5 tags for your publication" : undefined}
/>
</Grid>
{ensName && (
{ensNameList && (
<Grid item>
<Button
onClick={() => setOpenENSModal(true)}
Expand All @@ -255,7 +270,7 @@ export const SettingSection: React.FC<SettingsSectionProps> = ({ couldDelete, co
</Button>
</Grid>
)}
{ensName && (
{ensNameList && (
<EnsModal
open={openENSModal}
onClose={() => setOpenENSModal(false)}
Expand Down
7 changes: 5 additions & 2 deletions packages/app/src/services/ens/context/ens.context.tsx
Original file line number Diff line number Diff line change
@@ -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<EnsContextType>()

const EnsProvider = ({ children }: EnsProviderProps) => {

const [ensName, setEnsName] = useState<string | undefined | null>(undefined)
const [ensNameList, setEnsNameList] = useState<DropdownOption[]>([])

return (
<EnsContextProvider
value={{
ensName,
setEnsName
setEnsName,
ensNameList,
setEnsNameList,
}}
>
{children}
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/services/ens/context/ens.types.ts
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down
47 changes: 37 additions & 10 deletions packages/app/src/services/ens/hooks/useENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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]
}, [])
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -165,6 +191,7 @@ export const useENS = () => {
checkIfIsController,
checkIfIsOwner,
setTextRecord,
fetchNames,
loading,
transactionCompleted,
}
Expand Down
23 changes: 23 additions & 0 deletions packages/app/src/services/ens/queries.ts
Original file line number Diff line number Diff line change
@@ -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
}
}
}
}
}
`
28 changes: 28 additions & 0 deletions packages/app/src/services/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand All @@ -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),
Expand All @@ -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",
},
})

0 comments on commit 8117e82

Please sign in to comment.