diff --git a/src/components/TestnetWarning.tsx b/src/components/TestnetWarning.tsx index 73e0ce021..a94727886 100644 --- a/src/components/TestnetWarning.tsx +++ b/src/components/TestnetWarning.tsx @@ -1,7 +1,8 @@ // import { useSearchParams } from 'next/dist/client/components/navigation' import { useEffect, useState } from 'react' import styled, { css } from 'styled-components' -import { useAccount } from 'wagmi' + +import { getChainFromSubdomain, getChainFromUrl } from '@app/utils/utils' const Container = styled.div( ({ theme }) => css` @@ -41,7 +42,7 @@ const useGetConifguredChain = () => { */ export const TestnetWarning = () => { - const { chain } = useAccount() + const chain = getChainFromUrl() const [isClient, setIsClient] = useState(false) useEffect(() => { diff --git a/src/utils/query/wagmi.ts b/src/utils/query/wagmi.ts index bf1e26467..60663f6e8 100644 --- a/src/utils/query/wagmi.ts +++ b/src/utils/query/wagmi.ts @@ -1,4 +1,3 @@ -import { match } from 'ts-pattern' import { createClient, type FallbackTransport, type HttpTransport, type Transport } from 'viem' import { createConfig, createStorage, fallback, http } from 'wagmi' import { holesky, localhost, mainnet, sepolia } from 'wagmi/chains' @@ -12,6 +11,7 @@ import { sepoliaWithEns, } from '@app/constants/chains' +import { getChainFromSubdomain, getChainFromUrl } from '../utils' import { rainbowKitConnectors } from './wallets' const isLocalProvider = !!process.env.NEXT_PUBLIC_PROVIDER @@ -107,11 +107,7 @@ const wagmiConfig_ = createConfig({ storage: createStorage({ storage: localStorageWithInvertMiddleware(), key: prefix }), chains, client: () => { - const subdomain = typeof window !== 'undefined' ? window.location.hostname.split('.')[0] : '' - const chain = match(subdomain) - .with('sepolia', () => sepoliaWithEns) - .with('holesky', () => holeskyWithEns) - .otherwise(() => mainnetWithEns) + const chain = getChainFromUrl() const chainId = chain.id diff --git a/src/utils/utils.test.ts b/src/utils/utils.test.ts index fd75a6388..448f516f8 100644 --- a/src/utils/utils.test.ts +++ b/src/utils/utils.test.ts @@ -1,4 +1,6 @@ -import { describe, expect, it } from 'vitest' +import { afterEach, beforeEach, describe, expect, it } from 'vitest' + +import { holeskyWithEns, mainnetWithEns, sepoliaWithEns } from '@app/constants/chains' import { dateFromDateDiff } from './date' import { @@ -12,6 +14,7 @@ import { formatDurationOfDates, formatExpiry, formatFullExpiry, + getChainFromUrl, getEncodedLabelAmount, getLabelFromName, getResolverWrapperAwareness, @@ -131,7 +134,7 @@ describe('formatDurationOfDates', () => { formatDurationOfDates({ startDate: new Date(), endDate: new Date(Date.now() + 123 * 1000), - t: (x, options: any) => x + options?.count, + t: (x, options: any) => x + options?.count, }), ).toEqual('unit.days0') }) @@ -354,3 +357,81 @@ describe('getEncodedLabelAmount', () => { expect(result).toEqual(2) }) }) + +describe('getChainFromSubdomain', () => { + const originalWindow = { ...window } + + beforeEach(() => { + Object.defineProperty(window, 'location', { + value: { + hostname: '', + }, + writable: true, + }) + }) + + afterEach(() => { + window.location = originalWindow.location + }) + + it('should return sepoliaWithEns for valid sepolia subdomain', () => { + window.location.hostname = 'sepolia.app.ens.domains' + const chain = getChainFromUrl() + expect(chain).toBe(sepoliaWithEns) + }) + + it('should return holeskyWithEns for valid holesky subdomain', () => { + window.location.hostname = 'holesky.app.ens.domains' + const chain = getChainFromUrl() + expect(chain).toBe(holeskyWithEns) + }) + + it('should return mainnetWithEns for non-app.ens.domains hostname', () => { + window.location.hostname = 'sepolia.other.domain.com' + const chain = getChainFromUrl() + expect(chain).toBe(mainnetWithEns) + }) + + it('should return mainnetWithEns for hostname with incorrect segment count', () => { + window.location.hostname = 'test.sepolia.app.ens.domains' // 5 segments + const chain = getChainFromUrl() + expect(chain).toBe(mainnetWithEns) + }) + + it('should return mainnetWithEns when window is undefined', () => { + const originalWindow = global.window + // @ts-ignore + delete global.window + const chain = getChainFromUrl() + expect(chain).toBe(mainnetWithEns) + global.window = originalWindow + }) + + it('should use chain param for dev environment', () => { + window.location.hostname = 'test.ens-app-v3.pages.dev' + window.location.search = '?chain=sepolia' + const chain = getChainFromUrl() + expect(chain).toBe(sepoliaWithEns) + }) + + it('should use holesky chain param for dev environment', () => { + window.location.hostname = 'test.ens-app-v3.pages.dev' + window.location.search = '?chain=holesky' + const chain = getChainFromUrl() + expect(chain).toBe(holeskyWithEns) + }) + + it('should fallback to mainnet for dev environment with invalid chain param', () => { + window.location.hostname = 'test.ens-app-v3.pages.dev' + window.location.search = '?chain=invalid' + const chain = getChainFromUrl() + expect(chain).toBe(mainnetWithEns) + }) + + it('should ignore chain param for non-dev environment', () => { + window.location.hostname = 'sepolia.app.ens.domains' + window.location.search = '?chain=holesky' + const chain = getChainFromUrl() + expect(chain).toBe(sepoliaWithEns) + }) +}) diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 13c41f7d6..69bfbfdea 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,10 +1,12 @@ 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' @@ -220,3 +222,25 @@ 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) +}