diff --git a/next.config.mjs b/next.config.mjs
index ab28c31ab..4d2c16171 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -43,6 +43,40 @@ const nextConfig = {
images: {
domains: ['metadata.ens.domains'],
},
+ async headers() {
+ // keep this in case we need to debug Safe in the future
+ if (process.env.NODE_ENV === 'development') {
+ return [
+ {
+ source: '/manifest.json',
+ headers: [
+ {
+ key: 'Access-Control-Allow-Origin',
+ value: '*',
+ },
+ {
+ key: 'Access-Control-Allow-Methods',
+ value: 'GET, OPTIONS',
+ },
+ {
+ key: 'Access-Control-Allow-Headers',
+ value: 'X-Requested-With, content-type, Authorization',
+ },
+ ],
+ },
+ {
+ source: '/(.*)',
+ headers: [
+ {
+ key: 'Content-Security-Policy',
+ value: "frame-ancestors 'self' https://app.safe.global;",
+ },
+ ],
+ },
+ ]
+ }
+ return []
+ },
async rewrites() {
return [
{
diff --git a/public/manifest.json b/public/manifest.json
index b12e66b4a..9f416e18c 100644
--- a/public/manifest.json
+++ b/public/manifest.json
@@ -15,5 +15,6 @@
],
"theme_color": "#5298FF",
"background_color": "#F6F6F6",
- "display": "standalone"
+ "display": "standalone",
+ "description": "Decentralised naming for wallets, websites, & more"
}
diff --git a/src/components/@atoms/TextWithTooltip/TextWithTooltip.test.tsx b/src/components/@atoms/TextWithTooltip/TextWithTooltip.test.tsx
index faa00d8d0..b313ad406 100644
--- a/src/components/@atoms/TextWithTooltip/TextWithTooltip.test.tsx
+++ b/src/components/@atoms/TextWithTooltip/TextWithTooltip.test.tsx
@@ -1,46 +1,37 @@
-import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/react'
-import { TextWithTooltip } from './TextWithTooltip'
import { userEvent } from '@testing-library/user-event'
import { ThemeProvider } from 'styled-components'
+import { describe, expect, it } from 'vitest'
+
import { lightTheme } from '@ensdomains/thorin'
+import { TextWithTooltip } from './TextWithTooltip'
+
const renderWithTheme = (component: React.ReactNode) => {
- return render(
-
- {component}
-
- )
+ return render({component})
}
describe('TextWithTooltip', () => {
it('should render children and show tooltip on hover', async () => {
- renderWithTheme(
-
- Hover me
-
- )
-
+ renderWithTheme(Hover me)
+
const button = screen.getByText('Hover me')
expect(button).toBeInTheDocument()
-
+
await userEvent.hover(button)
expect(screen.getByText('This is a tooltip')).toBeInTheDocument()
})
it('should render learn more link when link prop is provided', async () => {
renderWithTheme(
-
+
With link
-
+ ,
)
-
+
const button = screen.getByText('With link')
await userEvent.hover(button)
-
+
const learnMoreLink = screen.getByText('action.learnMore')
expect(learnMoreLink).toBeInTheDocument()
expect(learnMoreLink.closest('a')).toHaveAttribute('href', 'https://example.com')
@@ -50,14 +41,12 @@ describe('TextWithTooltip', () => {
it('should not render learn more link when link prop is not provided', async () => {
renderWithTheme(
-
- No link
-
+ No link,
)
-
+
const button = screen.getByText('No link')
await userEvent.hover(button)
-
+
expect(screen.queryByText('action.learnMore')).not.toBeInTheDocument()
})
})
diff --git a/src/components/@molecules/DateSelection/DateSelection.tsx b/src/components/@molecules/DateSelection/DateSelection.tsx
index 3c19320e2..1e7e37225 100644
--- a/src/components/@molecules/DateSelection/DateSelection.tsx
+++ b/src/components/@molecules/DateSelection/DateSelection.tsx
@@ -7,6 +7,7 @@ import { Typography } from '@ensdomains/thorin'
import { Calendar } from '@app/components/@atoms/Calendar/Calendar'
import { PlusMinusControl } from '@app/components/@atoms/PlusMinusControl/PlusMinusControl'
import { roundDurationWithDay, secondsFromDateDiff } from '@app/utils/date'
+import { isInsideSafe } from '@app/utils/safe'
import { formatDurationOfDates, secondsToYears } from '@app/utils/utils'
const YearsViewSwitch = styled.button(
@@ -73,9 +74,11 @@ export const DateSelection = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dateInYears, durationType])
+ const isSafeApp = isInsideSafe()
+
return (
- {durationType === 'date' ? (
+ {durationType === 'date' && !isSafeApp ? (
{
@@ -113,13 +116,15 @@ export const DateSelection = ({
postFix: mode === 'register' ? ' registration. ' : ' extension. ',
t,
})}
- onChangeDurationType?.(durationType === 'years' ? 'date' : 'years')}
- >
- {t(`calendar.pick_by_${durationType === 'date' ? 'years' : 'date'}`, { ns: 'common' })}
-
+ {!isSafeApp && (
+ onChangeDurationType?.(durationType === 'years' ? 'date' : 'years')}
+ >
+ {t(`calendar.pick_by_${durationType === 'date' ? 'years' : 'date'}`, { ns: 'common' })}
+
+ )}
)
diff --git a/src/components/@molecules/NetworkNotifications/NetworkNotifications.test.tsx b/src/components/@molecules/NetworkNotifications/NetworkNotifications.test.tsx
index f0c6b3eca..eac0c137b 100644
--- a/src/components/@molecules/NetworkNotifications/NetworkNotifications.test.tsx
+++ b/src/components/@molecules/NetworkNotifications/NetworkNotifications.test.tsx
@@ -26,7 +26,7 @@ describe('NetworkNotifications', () => {
it('should show notification if shouldOpenModal sets true', () => {
vi.mocked(shouldOpenModal).mockReturnValue(true)
mockUseAccount.mockReturnValue({
- chainId: 1
+ chainId: 1,
})
mockUseChainId.mockReturnValue(1)
render()
@@ -36,7 +36,7 @@ describe('NetworkNotifications', () => {
it('should not show notification if shouldOpenModal sets false', () => {
vi.mocked(shouldOpenModal).mockReturnValue(false)
mockUseAccount.mockReturnValue({
- chainId: 1
+ chainId: 1,
})
mockUseChainId.mockReturnValue(1)
diff --git a/src/components/@molecules/NetworkNotifications/utils.test.ts b/src/components/@molecules/NetworkNotifications/utils.test.ts
index 4e1aa901a..d4dc0b02d 100644
--- a/src/components/@molecules/NetworkNotifications/utils.test.ts
+++ b/src/components/@molecules/NetworkNotifications/utils.test.ts
@@ -1,7 +1,9 @@
-import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
-import { shouldOpenModal } from './utils'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
+
import * as chains from '@app/constants/chains'
+import { shouldOpenModal } from './utils'
+
describe('shouldOpenModal', () => {
beforeEach(() => {
vi.resetModules()
diff --git a/src/components/@molecules/ProfileEditor/Avatar/AvatarNFT.test.tsx b/src/components/@molecules/ProfileEditor/Avatar/AvatarNFT.test.tsx
index cddf28b12..9af5a48c3 100644
--- a/src/components/@molecules/ProfileEditor/Avatar/AvatarNFT.test.tsx
+++ b/src/components/@molecules/ProfileEditor/Avatar/AvatarNFT.test.tsx
@@ -2,15 +2,14 @@
import { fireEvent, mockFunction, render, screen, userEvent, waitFor } from '@app/test-utils'
import * as ReactQuery from '@tanstack/react-query'
+import React from 'react'
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest'
import { useAccount, useClient } from 'wagmi'
-
import * as UseInfiniteQuery from '@app/utils/query/useInfiniteQuery'
import { makeMockIntersectionObserver } from '../../../../../test/mock/makeMockIntersectionObserver'
import { AvatarNFT } from './AvatarNFT'
-import React from 'react'
vi.mock('wagmi')
vi.mock('@app/hooks/chain/useCurrentBlockTimestamp', () => ({
@@ -20,8 +19,6 @@ vi.mock('@app/hooks/chain/useChainName', () => ({
useChainName: () => 'mainnet',
}))
-
-
const mockUseClient = mockFunction(useClient)
const mockUseAccount = mockFunction(useAccount)
@@ -197,7 +194,6 @@ describe('', () => {
it('show load more data on page load trigger', async () => {
const useInfiniteQuerySpy = vi.spyOn(UseInfiniteQuery, 'useInfiniteQuery')
-
mockFetch
.mockImplementationOnce(() =>
Promise.resolve({
@@ -214,15 +210,19 @@ describe('', () => {
}),
)
vi.mock('@ensdomains/thorin', async (importActual) => ({
- ...(await importActual() as any),
- ScrollBox: () => ({ children, onReachedBottom }: React.PropsWithChildren<{
- onReachedBottom?: () => void;
- }>) => {
- onReachedBottom!()
- return
{children}
- },
+ ...((await importActual()) as any),
+ ScrollBox:
+ () =>
+ ({
+ children,
+ onReachedBottom,
+ }: React.PropsWithChildren<{
+ onReachedBottom?: () => void
+ }>) => {
+ onReachedBottom!()
+ return {children}
+ },
}))
-
render()
await waitFor(() => expect(mockFetch).toHaveBeenCalled())
@@ -255,13 +255,18 @@ describe('', () => {
)
vi.mock('@ensdomains/thorin', async (importActual) => ({
- ...(await importActual() as any),
- ScrollBox: () => ({ children, onReachedBottom }: React.PropsWithChildren<{
- onReachedBottom?: () => void;
- }>) => {
- onReachedBottom!()
- return {children}
- },
+ ...((await importActual()) as any),
+ ScrollBox:
+ () =>
+ ({
+ children,
+ onReachedBottom,
+ }: React.PropsWithChildren<{
+ onReachedBottom?: () => void
+ }>) => {
+ onReachedBottom!()
+ return {children}
+ },
}))
render()
diff --git a/src/components/@molecules/TransactionDialogManager/stage/TransactionStageModal.test.tsx b/src/components/@molecules/TransactionDialogManager/stage/TransactionStageModal.test.tsx
index 30c71ba8c..b91646fcc 100644
--- a/src/components/@molecules/TransactionDialogManager/stage/TransactionStageModal.test.tsx
+++ b/src/components/@molecules/TransactionDialogManager/stage/TransactionStageModal.test.tsx
@@ -230,7 +230,7 @@ describe('TransactionStageModal', () => {
)
expect(screen.getByTestId('transaction-modal-confirm-button')).toBeDisabled()
await waitFor(() =>
- expect(screen.getByTestId('transaction-modal-confirm-button')).toBeEnabled()
+ expect(screen.getByTestId('transaction-modal-confirm-button')).toBeEnabled(),
)
expect(mockEstimateGas).toHaveBeenCalledTimes(1)
})
diff --git a/src/components/pages/import/[name]/steps/SelectImportType.test.tsx b/src/components/pages/import/[name]/steps/SelectImportType.test.tsx
index afc2c68d5..56a91d6ff 100644
--- a/src/components/pages/import/[name]/steps/SelectImportType.test.tsx
+++ b/src/components/pages/import/[name]/steps/SelectImportType.test.tsx
@@ -10,8 +10,8 @@ import { useAccount } from 'wagmi'
import { lightTheme } from '@ensdomains/thorin'
import { useDnsOffchainStatus } from '@app/hooks/dns/useDnsOffchainStatus'
-import { useUnmanagedTLD } from '@app/hooks/useUnmanagedTLD'
import { useResolver } from '@app/hooks/ensjs/public/useResolver'
+import { useUnmanagedTLD } from '@app/hooks/useUnmanagedTLD'
import i18n from '@app/i18n'
import { calculateDnsSteps, SelectImportType } from './SelectImportType'
@@ -208,7 +208,11 @@ describe('SelectImportType component', () => {
,
)
- expect(screen.getByText("The team behind .club have customized their ENS experience, so we're unable to help you import the name at this time")).toBeInTheDocument()
+ expect(
+ screen.getByText(
+ "The team behind .club have customized their ENS experience, so we're unable to help you import the name at this time",
+ ),
+ ).toBeInTheDocument()
})
it('should show customized TLD message for TLDs not managed by DNSRegistrar', () => {
@@ -230,7 +234,11 @@ describe('SelectImportType component', () => {
,
)
- expect(screen.getByText("The team behind .xyz have customized their ENS experience, so we're unable to help you import the name at this time")).toBeInTheDocument()
+ expect(
+ screen.getByText(
+ "The team behind .xyz have customized their ENS experience, so we're unable to help you import the name at this time",
+ ),
+ ).toBeInTheDocument()
})
it('should show normal import options for managed TLDs', () => {
diff --git a/src/hooks/abilities/useAbilities.test.ts b/src/hooks/abilities/useAbilities.test.ts
index 77a7ef9a1..059fba0ad 100644
--- a/src/hooks/abilities/useAbilities.test.ts
+++ b/src/hooks/abilities/useAbilities.test.ts
@@ -1,5 +1,6 @@
import { mockFunction, renderHook } from '@app/test-utils'
+import { dequal } from 'dequal'
import { match, P } from 'ts-pattern'
import { Address } from 'viem'
// import { writeFileSync} from 'fs'
@@ -20,7 +21,6 @@ import { useBasicName } from '../useBasicName'
import { useHasSubnames } from '../useHasSubnames'
import { useParentBasicName } from '../useParentBasicName'
import { useAbilities } from './useAbilities'
-import { dequal } from 'dequal'
vi.mock('@app/hooks/account/useAccountSafely')
vi.mock('@app/hooks/useBasicName')
diff --git a/src/transaction-flow/transaction/registerName.test.ts b/src/transaction-flow/transaction/registerName.test.ts
index e6ad2968f..8f6aee7b8 100644
--- a/src/transaction-flow/transaction/registerName.test.ts
+++ b/src/transaction-flow/transaction/registerName.test.ts
@@ -3,7 +3,7 @@ import { mockFunction } from '@app/test-utils'
import { expect, it, vi } from 'vitest'
import { getPrice } from '@ensdomains/ensjs/public'
-import { registerName, legacyRegisterName } from '@ensdomains/ensjs/wallet'
+import { legacyRegisterName, registerName } from '@ensdomains/ensjs/wallet'
import registerNameFlowTransaction from './registerName'
diff --git a/src/utils/query/wagmi.ts b/src/utils/query/wagmi.ts
index 96c0a48ca..1006abb11 100644
--- a/src/utils/query/wagmi.ts
+++ b/src/utils/query/wagmi.ts
@@ -6,6 +6,7 @@ import { ccipRequest } from '@ensdomains/ensjs/utils'
import { getChainsFromUrl, SupportedChain } from '@app/constants/chains'
+import { isInsideSafe } from '../safe'
import { rainbowKitConnectors } from './wallets'
const isLocalProvider = !!process.env.NEXT_PUBLIC_PROVIDER
@@ -92,7 +93,7 @@ const wagmiConfig_ = createConfig({
syncConnectedChain: false,
connectors: rainbowKitConnectors,
ssr: true,
- multiInjectedProviderDiscovery: true,
+ multiInjectedProviderDiscovery: !isInsideSafe(),
storage: createStorage({ storage: localStorageWithInvertMiddleware(), key: prefix }),
chains,
client: ({ chain }) => {
diff --git a/src/utils/query/wallets.ts b/src/utils/query/wallets.ts
index 892cc3c09..7f03c36c5 100644
--- a/src/utils/query/wallets.ts
+++ b/src/utils/query/wallets.ts
@@ -13,26 +13,29 @@ import {
} from '@getpara/rainbowkit/wallets'
import { WC_PROJECT_ID } from '../constants'
+import { isInsideSafe } from '../safe'
-export const rainbowKitWallets = [
- // injected / not always shown
- injectedWallet,
- safeWallet,
- braveWallet,
- () => ({
- ...phantomWallet(),
- iconUrl: async () => (await import('../../assets/PhantomWallet')).default,
- iconBackground: '#9A8AEE',
- downloadUrls: {},
- }),
- // always shown
- walletConnectWallet,
- rainbowWallet,
- coinbaseWallet,
- metaMaskWallet,
- ledgerWallet,
- argentWallet,
-] as const satisfies WalletList[number]['wallets']
+export const rainbowKitWallets = isInsideSafe()
+ ? [safeWallet]
+ : ([
+ // injected / not always shown
+ injectedWallet,
+ safeWallet,
+ braveWallet,
+ () => ({
+ ...phantomWallet(),
+ iconUrl: async () => (await import('../../assets/PhantomWallet')).default,
+ iconBackground: '#9A8AEE',
+ downloadUrls: {},
+ }),
+ // always shown
+ walletConnectWallet,
+ rainbowWallet,
+ coinbaseWallet,
+ metaMaskWallet,
+ ledgerWallet,
+ argentWallet,
+ ] as const satisfies WalletList[number]['wallets'])
export const rainbowKitConnectors = connectorsForWallets(
[
diff --git a/src/utils/registration/makeLegacyRegistrationParams.test.ts b/src/utils/registration/makeLegacyRegistrationParams.test.ts
index 9f65dffd1..f75ea83bf 100644
--- a/src/utils/registration/makeLegacyRegistrationParams.test.ts
+++ b/src/utils/registration/makeLegacyRegistrationParams.test.ts
@@ -1,8 +1,9 @@
import { describe, expect, expectTypeOf, it } from 'vitest'
-import { makeLegacyRegistrationParams } from './makeLegacyRegistrationParams'
import { RegistrationParameters } from '@ensdomains/ensjs/utils'
+import { makeLegacyRegistrationParams } from './makeLegacyRegistrationParams'
+
describe('makeLegacyRegistrationParams', () => {
it('should return owner as address if no eth record exists', () => {
const params: RegistrationParameters = {
@@ -32,7 +33,7 @@ describe('makeLegacyRegistrationParams', () => {
resolverAddress: '0xresolverAddress',
records: {
coins: [{ coin: 'eth', value: '0xother' }],
- }
+ },
}
expect(makeLegacyRegistrationParams(params)).toEqual({
diff --git a/src/utils/safe.ts b/src/utils/safe.ts
index d20228908..5febe224d 100644
--- a/src/utils/safe.ts
+++ b/src/utils/safe.ts
@@ -116,3 +116,6 @@ export const fetchTxFromSafeTxHash = async ({
transactionHash: data.txHash,
}
}
+
+// Only applies to Safe because of our CSP policy not allowing other iframes
+export const isInsideSafe = () => typeof window !== 'undefined' && window !== window.parent