Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: white label branding + domain checks #4593

Open
wants to merge 12 commits into
base: dev
Choose a base branch
from
2 changes: 2 additions & 0 deletions .github/workflows/build/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ runs:
NEXT_PUBLIC_SAFE_RELAY_SERVICE_URL_PRODUCTION: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_SAFE_GELATO_RELAY_SERVICE_URL_PRODUCTION }}
NEXT_PUBLIC_SAFE_RELAY_SERVICE_URL_STAGING: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_SAFE_GELATO_RELAY_SERVICE_URL_STAGING }}
NEXT_PUBLIC_IS_OFFICIAL_HOST: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_IS_OFFICIAL_HOST }}
NEXT_PUBLIC_BRAND_LOGO: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_BRAND_LOGO }}
NEXT_PUBLIC_BRAND_NAME: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_BRAND_NAME }}
NEXT_PUBLIC_BLOCKAID_CLIENT_ID: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_BLOCKAID_CLIENT_ID }}
NEXT_PUBLIC_SOCIAL_WALLET_OPTIONS_STAGING: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_SOCIAL_WALLET_OPTIONS_STAGING }}
NEXT_PUBLIC_SOCIAL_WALLET_OPTIONS_PRODUCTION: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_SOCIAL_WALLET_OPTIONS_PRODUCTION }}
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@ jobs:
package-manager: yarn
test-script: yarn test:ci
github-token: ${{ secrets.GITHUB_TOKEN }}
env:
NEXT_PUBLIC_IS_OFFICIAL_HOST: true
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Environment Variable Scope: Ensure that NEXT_PUBLIC_IS_OFFICIAL_HOST is necessary as a public environment variable. Public environment variables expose their values to the client-side JavaScript. If the variable contains sensitive information, consider an alternative solution.

  2. Hardcoded Values: The value true for NEXT_PUBLIC_IS_OFFICIAL_HOST is hardcoded. If this value needs to be dynamic or might change based on the environment, consider refactoring to extract this as a configurable parameter. Use environment-specific configuration or secrets management where applicable.

3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ Here's the list of all the environment variables:

| Env variable | Description |
| ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `NEXT_PUBLIC_BRAND_NAME` | The name of the app, defaults to "Wallet fork" |
| `NEXT_PUBLIC_BRAND_LOGO` | The URL of the app logo displayed in the header |

| `NEXT_PUBLIC_INFURA_TOKEN` | [Infura](https://docs.infura.io/infura/networks/ethereum/how-to/secure-a-project/project-id) RPC API token |
| `NEXT_PUBLIC_SAFE_APPS_INFURA_TOKEN` | Infura token for Safe Apps, falls back to `NEXT_PUBLIC_INFURA_TOKEN` |
| `NEXT_PUBLIC_IS_PRODUCTION` | Set to `true` to build a minified production app |
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No significant issues found in the added environment variables. Ensure that the naming convention aligns with existing variables and verify that the new variables are correctly utilized in the application to avoid future integration problems.

Expand Down
4 changes: 2 additions & 2 deletions src/components/address-book/ImportDialog/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import ErrorMessage from '@/components/tx/ErrorMessage'
import { Errors, logError } from '@/services/exceptions'
import FileUpload, { FileTypes, type FileInfo } from '@/components/common/FileUpload'
import ExternalLink from '@/components/common/ExternalLink'
import { HelpCenterArticle } from '@/config/constants'
import { BRAND_NAME, HelpCenterArticle } from '@/config/constants'

type AddressBookCSVRow = ['address', 'name', 'chainId']

Expand Down Expand Up @@ -152,7 +152,7 @@ const ImportDialog = ({ handleClose }: { handleClose: () => void }): ReactElemen
{error && <ErrorMessage>{error}</ErrorMessage>}

<Typography>
Only CSV files exported from a {'Safe{Wallet}'} can be imported.
Only CSV files exported from a {BRAND_NAME} can be imported.
<br />
<ExternalLink
href={HelpCenterArticle.ADDRESS_BOOK_DATA}
Expand Down
8 changes: 5 additions & 3 deletions src/components/common/Footer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { AppRoutes } from '@/config/routes'
import packageJson from '../../../../package.json'
import ExternalLink from '../ExternalLink'
import MUILink from '@mui/material/Link'
import { HELP_CENTER_URL, IS_DEV, IS_OFFICIAL_HOST } from '@/config/constants'
import { HELP_CENTER_URL } from '@/config/constants'
import { useIsOfficialHost } from '@/hooks/useIsOfficialHost'

const footerPages = [
AppRoutes.welcome.index,
Expand All @@ -32,6 +33,7 @@ const FooterLink = ({ children, href }: { children: ReactNode; href: string }):

const Footer = (): ReactElement | null => {
const router = useRouter()
const isOfficialHost = useIsOfficialHost()

if (!footerPages.some((path) => router.pathname.startsWith(path))) {
return null
Expand All @@ -44,7 +46,7 @@ const Footer = (): ReactElement | null => {
return (
<footer className={css.container}>
<ul>
{IS_OFFICIAL_HOST || IS_DEV ? (
{isOfficialHost ? (
<>
<li>
<Typography variant="caption">&copy;2022–{new Date().getFullYear()} Core Contributors GmbH</Typography>
Expand Down Expand Up @@ -74,7 +76,7 @@ const Footer = (): ReactElement | null => {
</li>
</>
) : (
<li>{'This is an unofficial distribution of Safe{Wallet}'}</li>
<li>This is an unofficial distribution of the app</li>
)}

<li>
Expand Down
4 changes: 4 additions & 0 deletions src/components/common/Header/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ jest.mock(
},
)

jest.mock('@/hooks/useIsOfficialHost', () => ({
useIsOfficialHost: () => true,
}))

describe('Header', () => {
beforeEach(() => {
jest.resetAllMocks()
Expand Down
7 changes: 5 additions & 2 deletions src/components/common/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { useHasFeature } from '@/hooks/useChains'
import Track from '@/components/common/Track'
import { OVERVIEW_EVENTS, OVERVIEW_LABELS } from '@/services/analytics'
import { useSafeTokenEnabled } from '@/hooks/useSafeTokenEnabled'
import { useIsOfficialHost } from '@/hooks/useIsOfficialHost'
import { BRAND_LOGO, BRAND_NAME } from '@/config/constants'

type HeaderProps = {
onMenuToggle?: Dispatch<SetStateAction<boolean>>
Expand All @@ -45,6 +47,7 @@ const Header = ({ onMenuToggle, onBatchToggle }: HeaderProps): ReactElement => {
const isSafeOwner = useIsSafeOwner()
const router = useRouter()
const enableWc = useHasFeature(FEATURES.NATIVE_WALLETCONNECT)
const isOfficialHost = useIsOfficialHost()

// If on the home page, the logo should link to the Accounts or Welcome page, otherwise to the home page
const logoHref = getLogoLink(router)
Expand Down Expand Up @@ -77,13 +80,13 @@ const Header = ({ onMenuToggle, onBatchToggle }: HeaderProps): ReactElement => {

<div className={classnames(css.element, css.logoMobile)}>
<Link href={logoHref} passHref>
<SafeLogoMobile alt="Safe logo" />
{isOfficialHost ? <SafeLogoMobile alt="Safe logo" /> : null}
</Link>
</div>

<div className={classnames(css.element, css.hideMobile, css.logo)}>
<Link href={logoHref} passHref>
<SafeLogo alt="Safe logo" />
{isOfficialHost ? <SafeLogo alt={BRAND_NAME} /> : BRAND_LOGO && <img src={BRAND_LOGO} alt={BRAND_NAME} />}
</Link>
</div>

Expand Down
1 change: 1 addition & 0 deletions src/components/common/Header/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
display: none;
}

.logo img,
.logo svg,
.logoMobile svg {
width: auto;
Expand Down
6 changes: 3 additions & 3 deletions src/components/common/MetaTags/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { IS_PRODUCTION } from '@/config/constants'
import { BRAND_NAME, IS_PRODUCTION } from '@/config/constants'
import { ContentSecurityPolicy, StrictTransportSecurity } from '@/config/securityHeaders'
import lightPalette from '@/components/theme/lightPalette'
import darkPalette from '@/components/theme/darkPalette'

const descriptionText = 'Safe{Wallet} is the most trusted smart account wallet on Ethereum with over $100B secured.'
const titleText = 'Safe{Wallet}'
const descriptionText = `${BRAND_NAME} is the most trusted smart account wallet on Ethereum with over $100B secured.`
const titleText = BRAND_NAME

const MetaTags = ({ prefetchUrl }: { prefetchUrl: string }) => (
<>
Expand Down
3 changes: 2 additions & 1 deletion src/components/safe-apps/AddCustomAppModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { useShareSafeAppUrl } from '@/components/safe-apps/hooks/useShareSafeApp

import css from './styles.module.css'
import ExternalLink from '@/components/common/ExternalLink'
import { BRAND_NAME } from '@/config/constants'

type Props = {
open: boolean
Expand Down Expand Up @@ -148,7 +149,7 @@ export const AddCustomAppModal = ({ open, onClose, onSave, safeAppsList }: Props
})}
/>
}
label="This Safe App is not part of Safe{Wallet} and I agree to use it at my own risk."
label={`This Safe App is not part of ${BRAND_NAME} and I agree to use it at my own risk.`}
sx={{ mt: 2 }}
/>

Expand Down
3 changes: 2 additions & 1 deletion src/components/safe-apps/AppFrame/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { useCustomAppCommunicator } from '@/hooks/safe-apps/useCustomAppCommunic
import { useSanctionedAddress } from '@/hooks/useSanctionedAddress'
import BlockedAddress from '@/components/common/BlockedAddress'
import { isSafePassApp } from '@/features/walletconnect/services/utils'
import { BRAND_NAME } from '@/config/constants'

const UNKNOWN_APP_NAME = 'Unknown Safe App'

Expand Down Expand Up @@ -128,7 +129,7 @@ const AppFrame = ({ appUrl, allowedFeaturesList, safeAppFromManifest, isNativeEm
<>
{!isNativeEmbed && (
<Head>
<title>{`Safe{Wallet} - Safe Apps${remoteApp ? ' - ' + remoteApp.name : ''}`}</title>
<title>{`${BRAND_NAME} - Safe Apps${remoteApp ? ' - ' + remoteApp.name : ''}`}</title>
</Head>
)}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Typography from '@mui/material/Typography'
import PagePlaceholder from '@/components/common/PagePlaceholder'
import AddCustomAppIcon from '@/public/images/apps/add-custom-app.svg'
import { BRAND_NAME } from '@/config/constants'

const SafeAppsZeroResultsPlaceholder = ({ searchQuery }: { searchQuery: string }) => {
return (
Expand All @@ -9,7 +10,7 @@ const SafeAppsZeroResultsPlaceholder = ({ searchQuery }: { searchQuery: string }
text={
<Typography variant="body1" color="primary.light" m={2} maxWidth="600px">
No Safe Apps found matching <strong>{searchQuery}</strong>. Connect to dApps that haven&apos;t yet been
integrated with the {'Safe{Wallet}'} using WalletConnect.
integrated with the {BRAND_NAME} using WalletConnect.
</Typography>
}
/>
Expand Down
5 changes: 3 additions & 2 deletions src/components/settings/DataManagement/ImportFileUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { Dispatch, SetStateAction } from 'react'

import FileUpload, { FileTypes } from '@/components/common/FileUpload'
import InfoIcon from '@/public/images/notifications/info.svg'
import { BRAND_NAME } from '@/config/constants'

const AcceptedMimeTypes = {
'application/json': ['.json'],
Expand Down Expand Up @@ -52,7 +53,7 @@ export const ImportFileUpload = ({

return (
<>
<Typography>Import {'Safe{Wallet}'} data by uploading a file in the area below.</Typography>
<Typography>Import {BRAND_NAME} data by uploading a file in the area below.</Typography>

<FileUpload
fileType={FileTypes.JSON}
Expand All @@ -74,7 +75,7 @@ export const ImportFileUpload = ({
mr: 0.5,
}}
/>
Only JSON files exported from the {'Safe{Wallet}'} can be imported.
Only JSON files exported from the {BRAND_NAME} can be imported.
</Typography>
</>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const enum SAFE_EXPORT_VERSION {
}

export enum ImportErrors {
INVALID_VERSION = 'The file is not a Safe{Wallet} export.',
INVALID_VERSION = 'The file is not a valid export.',
INVALID_JSON_FORMAT = 'The JSON format is invalid.',
NO_IMPORT_DATA_FOUND = 'This file contains no importable data.',
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/settings/FallbackHandler/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { ReactElement } from 'react'

import EthHashInfo from '@/components/common/EthHashInfo'
import useSafeInfo from '@/hooks/useSafeInfo'
import { HelpCenterArticle } from '@/config/constants'
import { BRAND_NAME, HelpCenterArticle } from '@/config/constants'
import ExternalLink from '@/components/common/ExternalLink'
import { useTxBuilderApp } from '@/hooks/safe-apps/useTxBuilderApp'
import { useCurrentChain } from '@/hooks/useChains'
Expand Down Expand Up @@ -43,7 +43,7 @@ export const FallbackHandler = (): ReactElement | null => {

const warning = !hasFallbackHandler ? (
<>
The {'Safe{Wallet}'} may not work correctly as no fallback handler is currently set.
The {BRAND_NAME} may not work correctly as no fallback handler is currently set.
{txBuilder && (
<>
{' '}
Expand Down
6 changes: 3 additions & 3 deletions src/components/settings/SafeAppsSigningMethod/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { SETTINGS_EVENTS, trackEvent } from '@/services/analytics'
import { useAppDispatch, useAppSelector } from '@/store'
import { selectOnChainSigning, setOnChainSigning } from '@/store/settingsSlice'
import { FormControlLabel, Checkbox, Paper, Typography, FormGroup, Grid } from '@mui/material'
import { HelpCenterArticle } from '@/config/constants'
import { BRAND_NAME, HelpCenterArticle } from '@/config/constants'

export const SafeAppsSigningMethod = () => {
const onChainSigning = useAppSelector(selectOnChainSigning)
Expand All @@ -26,8 +26,8 @@ export const SafeAppsSigningMethod = () => {

<Grid item xs>
<Typography mb={2}>
This setting determines how the {'Safe{Wallet}'} will sign message requests from Safe Apps. Gasless,
off-chain signing is used by default. Learn more about message signing{' '}
This setting determines how the {BRAND_NAME} will sign message requests from Safe Apps. Gasless, off-chain
signing is used by default. Learn more about message signing{' '}
<ExternalLink href={HelpCenterArticle.SIGNED_MESSAGES}>here</ExternalLink>.
</Typography>
<FormGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import useSafeInfo from '@/hooks/useSafeInfo'
import InfoIcon from '@/public/images/notifications/info.svg'
import { RecovererWarning } from './RecovererSmartContractWarning'
import ExternalLink from '@/components/common/ExternalLink'
import { HelpCenterArticle, HelperCenterArticleTitles } from '@/config/constants'
import { BRAND_NAME, HelpCenterArticle, HelperCenterArticleTitles } from '@/config/constants'
import { TOOLTIP_TITLES } from '../../common/constants'
import Track from '@/components/common/Track'
import type { RecoveryStateItem } from '@/features/recovery/services/recovery-state'
Expand Down Expand Up @@ -281,7 +281,7 @@ export function UpsertRecoveryFlowSettings({
<TxCard>
<FormControlLabel
data-testid="warning-section"
label="I understand that the Recoverer will be able to initiate recovery of this Safe Account and that I will only be informed within the Safe{Wallet}."
label={`I understand that the Recoverer will be able to initiate recovery of this Safe Account and that I will only be informed within the ${BRAND_NAME}.`}
control={<Checkbox checked={understandsRisk} onChange={(_, checked) => setUnderstandsRisk(checked)} />}
sx={{ pl: 2 }}
/>
Expand Down
3 changes: 3 additions & 0 deletions src/config/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ export const TWITTER_URL = 'https://twitter.com/safe'

// Legal
export const IS_OFFICIAL_HOST = process.env.NEXT_PUBLIC_IS_OFFICIAL_HOST === 'true'
export const OFFICIAL_HOSTS = /app\.safe\.global|.+\.5afe\.dev|localhost:3000/
export const BRAND_NAME = process.env.NEXT_PUBLIC_BRAND_NAME || (IS_OFFICIAL_HOST ? 'Safe{Wallet}' : 'Wallet fork')
export const BRAND_LOGO = process.env.NEXT_PUBLIC_BRAND_LOGO || ''

// Risk mitigation (Blockaid)
export const BLOCKAID_API = 'https://client.blockaid.io'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { formatDurationFromMilliseconds, formatVisualAmount, maybePlural } from
import { formatCurrency } from '@/utils/formatNumber'
import StakingStatus from '@/features/stake/components/StakingStatus'
import { InfoTooltip } from '@/features/stake/components/InfoTooltip'
import { BRAND_NAME } from '@/config/constants'

type StakingOrderConfirmationViewProps = {
order: NativeStakingDepositConfirmationView | StakingTxDepositInfo
Expand Down Expand Up @@ -55,7 +56,9 @@ const StakingConfirmationTxDeposit = ({ order, isTxDetails }: StakingOrderConfir
title={
<>
Fee
<InfoTooltip title="The widget fee incurred here is charged by Kiln for the operation of this widget. The fee is calculated automatically. Part of the fee will contribute to a license fee that supports the Safe Community. Neither the Safe Ecosystem Foundation nor Safe{Wallet} operates the Kiln Widget and/or Kiln." />
<InfoTooltip
title={`The widget fee incurred here is charged by Kiln for the operation of this widget. The fee is calculated automatically. Part of the fee will contribute to a license fee that supports the Safe Community. Neither the Safe Ecosystem Foundation nor ${BRAND_NAME} operates the Kiln Widget and/or Kiln.`}
/>
</>
}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { SwapOrderConfirmationView, TwapOrderConfirmationView } from '@safe-global/safe-gateway-typescript-sdk'
import { getOrderFeeBps } from '@/features/swap/helpers/utils'
import { DataRow } from '@/components/common/Table/DataRow'
import { HelpCenterArticle } from '@/config/constants'
import { BRAND_NAME, HelpCenterArticle } from '@/config/constants'
import { HelpIconTooltip } from '@/features/swap/components/HelpIconTooltip'
import MUILink from '@mui/material/Link'

Expand All @@ -24,7 +24,7 @@ export const OrderFeeConfirmationView = ({
<>
The tiered widget fee incurred here is charged by CoW Protocol for the operation of this widget. The fee is
automatically calculated into this quote. Part of the fee will contribute to a license fee that supports the
Safe Community. Neither the Safe Ecosystem Foundation nor {`Safe{Wallet}`} operate the CoW Swap Widget
Safe Community. Neither the Safe Ecosystem Foundation nor {`${BRAND_NAME}`} operate the CoW Swap Widget
and/or CoW Swap.
<MUILink href={HelpCenterArticle.SWAP_WIDGET_FEES} target="_blank" rel="noopener noreferrer">
Learn more
Expand Down
4 changes: 2 additions & 2 deletions src/features/swap/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { FEATURES } from '@/utils/chains'
import { useGetIsSanctionedQuery } from '@/store/api/ofac'
import { skipToken } from '@reduxjs/toolkit/query/react'
import { getKeyWithTrueValue } from '@/utils/helpers'
import { BRAND_NAME } from '@/config/constants'

const BASE_URL = typeof window !== 'undefined' && window.location.origin ? window.location.origin : ''

Expand Down Expand Up @@ -148,8 +149,7 @@ const SwapWidget = ({ sell }: Params) => {
},
content: {
feeLabel: 'Widget Fee',
feeTooltipMarkdown:
'The [tiered widget fee](https://help.safe.global/en/articles/178530-how-does-the-widget-fee-work-for-native-swaps) incurred here is charged by CoW Protocol for the operation of this widget. The fee is automatically calculated into this quote. Part of the fee will contribute to a license fee that supports the Safe Community. Neither the Safe Ecosystem Foundation nor Safe{Wallet} operate the CoW Swap Widget and/or CoW Swap',
feeTooltipMarkdown: `The [tiered widget fee](https://help.safe.global/en/articles/178530-how-does-the-widget-fee-work-for-native-swaps) incurred here is charged by CoW Protocol for the operation of this widget. The fee is automatically calculated into this quote. Part of the fee will contribute to a license fee that supports the Safe Community. Neither the Safe Ecosystem Foundation nor ${BRAND_NAME} operate the CoW Swap Widget and/or CoW Swap`,
},
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import css from './styles.module.css'
import useSafeInfo from '@/hooks/useSafeInfo'
import Track from '@/components/common/Track'
import { WALLETCONNECT_EVENTS } from '@/services/analytics/events/walletconnect'
import { BRAND_NAME } from '@/config/constants'

const WC_HINTS_KEY = 'wcHints'

Expand Down Expand Up @@ -60,7 +61,7 @@ export const WcConnectionForm = ({ sessions, uri }: { sessions: SessionTypes.Str
}}
>
{safeLoaded
? `Paste the pairing code below to connect to your Safe{Wallet} via WalletConnect`
? `Paste the pairing code below to connect to your ${BRAND_NAME} via WalletConnect`
: `Please open one of your Safe Accounts to connect to via WalletConnect`}
</Typography>

Expand Down
Loading
Loading