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