diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 51882e3db..feb1582c5 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -419,5 +419,22 @@
"ownerNotManager": "You must be connected as the Manager of this name to set the verification record. You can view and update the Manager under the Ownership tab.",
"wrongAccount": "You must be connected as {{ nameOrAddress }} to set the verification record.",
"default": "We could't verify your account. Please return to Dentity and try again."
+ },
+ "networkNotifications": {
+ "Ethereum": {
+ "title": "Switch to Ethereum?",
+ "description": "You've selected Ethereum (mainnet) in your wallet.",
+ "action": "Go to app.ens.domains"
+ },
+ "Sepolia": {
+ "title": "Switch to Sepolia?",
+ "description": "You've selected Sepolia (testnet) in your wallet.",
+ "action": "Go to sepolia.app.ens.domains"
+ },
+ "Holesky": {
+ "title": "Switch to Holesky?",
+ "description": "You've selected Holesky (testnet) in your wallet.",
+ "action": "Go to holesky.app.ens.domains"
+ }
}
}
diff --git a/src/components/NetworkNotifications.test.tsx b/src/components/NetworkNotifications.test.tsx
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/components/NetworkNotifications.tsx b/src/components/NetworkNotifications.tsx
new file mode 100644
index 000000000..3a728a105
--- /dev/null
+++ b/src/components/NetworkNotifications.tsx
@@ -0,0 +1,54 @@
+import { useEffect, useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import styled, { css } from 'styled-components'
+import { useAccount } from 'wagmi'
+
+import { Button, Toast } from '@ensdomains/thorin'
+
+import { getChainsFromUrl, getSupportedChainById } from '@app/constants/chains'
+
+const appLinks = {
+ ethereum: 'app.ens.domains',
+ sepolia: 'sepolia.app.ens.domains',
+ holesky: 'holesky.app.ens.domains',
+}
+
+export const NetworkNotifications = () => {
+ const { t } = useTranslation()
+ const account = useAccount()
+ const [open, setOpen] = useState(false)
+
+ const connectedChainName = account?.chain?.name
+ const connectedChainId = account?.chain?.id
+
+ console.log('connectedChainName: ', connectedChainName)
+
+ useEffect(() => {
+ if (!connectedChainName) return
+ if (!getSupportedChainById(connectedChainId)) return
+
+ const currentChain = getChainsFromUrl()?.[0]
+ if (currentChain?.id !== connectedChainId) {
+ setOpen(true)
+ }
+ }, [connectedChainName, connectedChainId])
+
+ return (
+ setOpen(false)}
+ >
+
+
+ )
+}
diff --git a/src/components/TestnetWarning.tsx b/src/components/TestnetWarning.tsx
index a94727886..647096330 100644
--- a/src/components/TestnetWarning.tsx
+++ b/src/components/TestnetWarning.tsx
@@ -1,8 +1,7 @@
-// import { useSearchParams } from 'next/dist/client/components/navigation'
import { useEffect, useState } from 'react'
import styled, { css } from 'styled-components'
-import { getChainFromSubdomain, getChainFromUrl } from '@app/utils/utils'
+import { getChainsFromUrl } from '@app/constants/chains'
const Container = styled.div(
({ theme }) => css`
@@ -15,41 +14,15 @@ const Container = styled.div(
`,
)
-/*
-const getChainFromQueryString = (searchParams: URLSearchParams) => {
- // Query param only possible in test/dev
- if (
- !(
- window.location.hostname.endsWith('localhost') ||
- window.location.hostname.endsWith('ens.pages.dev')
- )
- )
- return
-
- // If requested chain id is supported return it, otherwise default to mainnet
- searchParams.get('chainId')
-}
-
-const getChainFromSubdomain = (subdomain: string) => {
- // if (subdomain === 'testnet') return chains[0]
- // if (subdomain === 'beta') return chains[1]
- // return chains[2]
-}
-
-const useGetConifguredChain = () => {
- const searchParams = useSearchParams()
-}
- */
-
export const TestnetWarning = () => {
- const chain = getChainFromUrl()
+ const chains = getChainsFromUrl()
const [isClient, setIsClient] = useState(false)
useEffect(() => {
setIsClient(true)
}, [])
- if (isClient && chain && chain.id !== 1)
- return You are viewing the ENS app on {chain.name} testnet.
+ if (isClient && chains && chains[0].id !== 1)
+ return You are viewing the ENS app on {chains[0].name} testnet.
return null
}
diff --git a/src/components/Notifications.test.tsx b/src/components/TransactionNotifications.test.tsx
similarity index 86%
rename from src/components/Notifications.test.tsx
rename to src/components/TransactionNotifications.test.tsx
index 8c329f18a..af6fcd843 100644
--- a/src/components/Notifications.test.tsx
+++ b/src/components/TransactionNotifications.test.tsx
@@ -8,7 +8,7 @@ import type { Transaction } from '@app/hooks/transactions/transactionStore'
import { useBreakpoint } from '@app/utils/BreakpointProvider'
import { UpdateCallback, useCallbackOnTransaction } from '@app/utils/SyncProvider/SyncProvider'
-import { Notifications } from './Notifications'
+import { TransactionNotifications } from './TransactionNotifications'
vi.mock('@app/hooks/chain/useChainName')
vi.mock('@app/utils/SyncProvider/SyncProvider')
@@ -45,15 +45,15 @@ describe('Notifications', () => {
})
mockUseChainName.mockReturnValue('mainnet')
it('should not render a toast if there is no transactions', () => {
- render()
+ render()
expect(screen.queryByTestId('toast-desktop')).not.toBeInTheDocument()
})
it('should render a toast when a pending transaction is confirmed', async () => {
- const { rerender } = render()
+ const { rerender } = render()
expect(screen.queryByTestId('toast-desktop')).not.toBeInTheDocument()
cb(makeRecentTransaction('confirmed')(null, 0))
- rerender()
+ rerender()
await waitFor(() => screen.queryByTestId('toast-desktop'), {
timeout: 500,
@@ -62,13 +62,13 @@ describe('Notifications', () => {
it('should show a new notification on dismiss if there is one queued', async () => {
const mockData = Array.from({ length: 2 }, makeRecentTransaction('pending'))
- const { rerender } = render()
+ const { rerender } = render()
expect(screen.queryByTestId('toast-desktop')).not.toBeInTheDocument()
cb({ ...mockData[0], status: 'confirmed1' as any })
cb({ ...mockData[1], status: 'confirmed2' as any })
- rerender()
+ rerender()
await waitFor(() => screen.queryByText('transaction.status.confirmed1.notifyTitle'), {
timeout: 500,
@@ -83,12 +83,12 @@ describe('Notifications', () => {
}).then((el) => expect(el).toBeInTheDocument())
})
it('should show the correct title and description for a notification', async () => {
- const { rerender } = render()
+ const { rerender } = render()
const mockData = makeRecentTransaction('confirmed')(null, 0)
cb(mockData)
- rerender()
+ rerender()
await waitFor(() => screen.queryByTestId('toast-desktop'), {
timeout: 500,
@@ -98,12 +98,12 @@ describe('Notifications', () => {
expect(screen.getByText('transaction.status.confirmed.notifyMessage')).toBeInTheDocument()
})
it('should not show a notification for a repriced transaction', async () => {
- const { rerender } = render()
+ const { rerender } = render()
const mockData = makeRecentTransaction('repriced')(null, 0)
cb(mockData)
- rerender()
+ rerender()
await waitFor(() => screen.queryByTestId('toast-desktop'), {
timeout: 500,
diff --git a/src/components/Notifications.tsx b/src/components/TransactionNotifications.tsx
similarity index 98%
rename from src/components/Notifications.tsx
rename to src/components/TransactionNotifications.tsx
index 811050a68..44eb73292 100644
--- a/src/components/Notifications.tsx
+++ b/src/components/TransactionNotifications.tsx
@@ -28,7 +28,7 @@ const ButtonContainer = styled.div(
`,
)
-export const Notifications = () => {
+export const TransactionNotifications = () => {
const { t } = useTranslation()
const breakpoints = useBreakpoint()
diff --git a/src/constants/chains.ts b/src/constants/chains.ts
index 4bb193d91..9316bf212 100644
--- a/src/constants/chains.ts
+++ b/src/constants/chains.ts
@@ -1,3 +1,4 @@
+import { match } from 'ts-pattern'
import { holesky } from 'viem/chains'
import { localhost, mainnet, sepolia } from 'wagmi/chains'
@@ -6,6 +7,8 @@ import { addEnsContracts } from '@ensdomains/ensjs'
import type { Register } from '@app/local-contracts'
import { makeLocalhostChainWithEns } from '@app/utils/chains/makeLocalhostChainWithEns'
+const isLocalProvider = !!process.env.NEXT_PUBLIC_PROVIDER
+
export const deploymentAddresses = JSON.parse(
process.env.NEXT_PUBLIC_DEPLOYMENT_ADDRESSES || '{}',
) as Register['deploymentAddresses']
@@ -43,3 +46,46 @@ export type SupportedChain =
| typeof sepoliaWithEns
| typeof holeskyWithEns
| typeof localhostWithEns
+
+export const getChainsFromUrl = () => {
+ if (typeof window === 'undefined')
+ return [
+ ...(isLocalProvider ? ([localhostWithEns] as const) : ([] as const)),
+ sepoliaWithEns,
+ mainnetWithEns,
+ holeskyWithEns,
+ ]
+
+ const { hostname, search } = window.location
+ const params = new URLSearchParams(search)
+ const chainParam = params.get('chain')
+ const segments = hostname.split('.')
+
+ if (segments.length === 4 && segments.slice(1).join('.') === 'ens-app-v3.pages.dev') {
+ if (chainParam === 'sepolia') return sepoliaWithEns
+ if (chainParam === 'holesky') return holeskyWithEns
+ }
+
+ if (!hostname.includes('app.ens.domains')) return mainnetWithEns
+ if (segments.length !== 4) return mainnetWithEns
+
+ return match(segments[0])
+ .with('sepolia', () => [
+ ...(isLocalProvider ? ([localhostWithEns] as const) : ([] as const)),
+ sepoliaWithEns,
+ mainnetWithEns,
+ holeskyWithEns,
+ ])
+ .with('holesky', () => [
+ ...(isLocalProvider ? ([localhostWithEns] as const) : ([] as const)),
+ holeskyWithEns,
+ sepoliaWithEns,
+ mainnetWithEns,
+ ])
+ .otherwise(() => [
+ ...(isLocalProvider ? ([localhostWithEns] as const) : ([] as const)),
+ mainnetWithEns,
+ holeskyWithEns,
+ sepoliaWithEns,
+ ])
+}
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index 9f91affa6..dc0d9a0e6 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -2,15 +2,21 @@ import '@splidejs/react-splide/css'
import { NextPage } from 'next'
import type { AppProps } from 'next/app'
-import { ReactElement, ReactNode } from 'react'
+import { ReactElement, ReactNode, useState } from 'react'
import { I18nextProvider } from 'react-i18next'
import { IntercomProvider } from 'react-use-intercom'
import { createGlobalStyle, keyframes, ThemeProvider } from 'styled-components'
-import { ThorinGlobalStyles, lightTheme as thorinLightTheme } from '@ensdomains/thorin'
+import {
+ Button,
+ ThorinGlobalStyles,
+ lightTheme as thorinLightTheme,
+ Toast,
+} from '@ensdomains/thorin'
-import { Notifications } from '@app/components/Notifications'
+import { NetworkNotifications } from '@app/components/NetworkNotifications'
import { TestnetWarning } from '@app/components/TestnetWarning'
+import { TransactionNotifications } from '@app/components/TransactionNotifications'
import { TransactionStoreProvider } from '@app/hooks/transactions/TransactionStoreContext'
import { Basic } from '@app/layouts/Basic'
import { TransactionFlowProvider } from '@app/transaction-flow/TransactionFlowProvider'
@@ -145,7 +151,8 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
-
+
+
{getLayout()}
diff --git a/src/utils/query/wagmi.ts b/src/utils/query/wagmi.ts
index 7857374c8..461f26266 100644
--- a/src/utils/query/wagmi.ts
+++ b/src/utils/query/wagmi.ts
@@ -4,9 +4,8 @@ import { holesky, localhost, mainnet, sepolia } from 'wagmi/chains'
import { ccipRequest } from '@ensdomains/ensjs/utils'
-import { localhostWithEns } from '@app/constants/chains'
+import { getChainsFromUrl } from '@app/constants/chains'
-import { getChainFromUrl } from '../utils'
import { rainbowKitConnectors } from './wallets'
const isLocalProvider = !!process.env.NEXT_PUBLIC_PROVIDER
@@ -73,12 +72,6 @@ const localStorageWithInvertMiddleware = (): Storage | undefined => {
}
}
-const getChain = () => {
- if (isLocalProvider) return localhostWithEns
- const chain = getChainFromUrl()
- return chain
-}
-
const transports = {
...(isLocalProvider
? ({
@@ -99,7 +92,7 @@ const wagmiConfig_ = createConfig({
ssr: true,
multiInjectedProviderDiscovery: true,
storage: createStorage({ storage: localStorageWithInvertMiddleware(), key: prefix }),
- chains: [getChain()],
+ chains: [...getChainsFromUrl()],
client: ({ chain }) => {
const chainId = chain.id
diff --git a/src/utils/utils.ts b/src/utils/utils.ts
index 69bfbfdea..13c41f7d6 100644
--- a/src/utils/utils.ts
+++ b/src/utils/utils.ts
@@ -1,12 +1,10 @@
import type { TFunction } from 'react-i18next'
-import { match } from 'ts-pattern'
import { toBytes, type Address } from 'viem'
import { Eth2ldName } from '@ensdomains/ensjs/dist/types/types'
import { GetPriceReturnType } from '@ensdomains/ensjs/public'
import { DecodedFuses } from '@ensdomains/ensjs/utils'
-import { holeskyWithEns, mainnetWithEns, sepoliaWithEns } from '@app/constants/chains'
import { KNOWN_RESOLVER_DATA } from '@app/constants/resolverAddressData'
import { CURRENCY_FLUCTUATION_BUFFER_PERCENTAGE } from './constants'
@@ -222,25 +220,3 @@ export const hslToHex = (hsl: string) => {
}
return `#${f(0)}${f(8)}${f(4)}`
}
-
-export const getChainFromUrl = () => {
- if (typeof window === 'undefined') return mainnetWithEns
-
- const { hostname, search } = window.location
- const params = new URLSearchParams(search)
- const chainParam = params.get('chain')
- const segments = hostname.split('.')
-
- if (segments.length === 4 && segments.slice(1).join('.') === 'ens-app-v3.pages.dev') {
- if (chainParam === 'sepolia') return sepoliaWithEns
- if (chainParam === 'holesky') return holeskyWithEns
- }
-
- if (!hostname.includes('app.ens.domains')) return mainnetWithEns
- if (segments.length !== 4) return mainnetWithEns
-
- return match(segments[0])
- .with('sepolia', () => sepoliaWithEns)
- .with('holesky', () => holeskyWithEns)
- .otherwise(() => mainnetWithEns)
-}