{
{/* edit batch */}
-
+
{
event.stopPropagation()
@@ -131,13 +101,7 @@ const TransactionLibrary = () => {
{/* download batch */}
-
+
{
event.stopPropagation()
@@ -149,13 +113,7 @@ const TransactionLibrary = () => {
{/* delete batch */}
-
+
{
@@ -180,24 +138,23 @@ const TransactionLibrary = () => {
))
) : (
-
+
{/* Empty library Screen */}
-
-
- You don't have any saved batches.
-
-
- Safe a batch by{' '}
-
-
+ {mode === EModes.DARK ? : }
+
+ You don't have any saved batches.
+
+ Safe a batch by{' '}
+
in transaction list view.
-
-
+
+
)}
{showDeleteBatchModal && batchToRemove && (
@@ -220,22 +177,16 @@ const TransactionLibrary = () => {
export default TransactionLibrary
-const Wrapper = styled.main`
+const StyledTitle = styled(Typography)`
&& {
- padding: 48px;
- padding-top: 120px;
- max-width: 650px;
- margin: 0 auto;
+ margin-top: 0px;
+ margin-bottom: 16px;
+ font-size: 20px;
+ font-weight: 700;
+ line-height: normal;
}
`
-const StyledTitle = styled(Title)`
- margin-top: 0px;
- margin-bottom: 16px;
- font-size: 20px;
- line-height: normal;
-`
-
const StyledAccordion = styled(Accordion)`
&.MuiAccordion-root {
margin-bottom: 0;
@@ -247,6 +198,12 @@ const StyledAccordion = styled(Accordion)`
const StyledAccordionSummary = styled(AccordionSummary)`
height: 64px;
+ &.MuiAccordionSummary-root,
+ &.MuiAccordionSummary-root.Mui-expanded,
+ &.MuiAccordionSummary-root:hover {
+ background-color: ${({ theme }) => theme.palette.background.paper};
+ }
+
& > .MuiAccordionSummary-content {
display: flex;
align-items: center;
@@ -263,10 +220,13 @@ const TransactionCounterDot = styled(Dot)`
height: 24px;
width: 24px;
min-width: 24px;
- background-color: #566976;
flex-shrink: 0;
`
+const StyledDotText = styled(Text)`
+ color: ${({ theme }) => theme.palette.background.paper};
+`
+
const StyledBatchTitle = styled.div`
padding-left: 4px;
min-width: 10px;
@@ -295,28 +255,22 @@ const StyledIconButton = styled(IconButton)`
&.MuiIconButton-root {
border-radius: 4px;
margin-left: 8px;
- background-color: #f6f7f8;
}
`
const StyledEmptyLibraryText = styled(Text)`
- max-width: 320px;
- margin-top: 32px;
- font-size: 20px;
- color: #566976;
-`
-
-const StyledEmptyLibraryTextLink = styled(Text)`
- margin-top: 8px;
- color: #566976;
- text-decoration: none;
+ && {
+ max-width: 320px;
+ margin-bottom: 8px;
+ color: ${({ theme }) => theme.palette.text.secondary};
+ }
`
const StyledLinkIcon = styled(Icon)`
vertical-align: middle;
margin-right: 2px;
-`
-const StyledEmptyLibraryLink = styled(Link)`
- text-decoration: none;
+ .icon-color {
+ fill: ${({ theme }) => theme.palette.text.secondary};
+ }
`
diff --git a/apps/tx-builder/src/test-utils.tsx b/apps/tx-builder/src/test-utils.tsx
index 4ba6d6dbe..40ba31c8e 100644
--- a/apps/tx-builder/src/test-utils.tsx
+++ b/apps/tx-builder/src/test-utils.tsx
@@ -1,20 +1,24 @@
import { ReactElement } from 'react'
import { ThemeProvider } from 'styled-components'
import { render, RenderResult } from '@testing-library/react'
-import { theme } from '@gnosis.pm/safe-react-components'
import { SafeProvider } from '@safe-global/safe-apps-react-sdk'
import { BrowserRouter } from 'react-router-dom'
import StoreProvider from './store'
+import SafeThemeProvider from './theme/SafeThemeProvider'
const renderWithProviders = (Components: ReactElement): RenderResult => {
return render(
-
-
-
- {Components}
-
-
- ,
+
+ {theme => (
+
+
+
+ {Components}
+
+
+
+ )}
+ ,
)
}
diff --git a/apps/tx-builder/src/theme/SafeThemeProvider.tsx b/apps/tx-builder/src/theme/SafeThemeProvider.tsx
new file mode 100644
index 000000000..af0f837eb
--- /dev/null
+++ b/apps/tx-builder/src/theme/SafeThemeProvider.tsx
@@ -0,0 +1,47 @@
+import React, { useEffect, useMemo, useState, type FC } from 'react'
+import { type Theme } from '@mui/material'
+import { ThemeProvider } from '@material-ui/core'
+import createSafeTheme from './safeTheme'
+import { getSDKVersion } from '@safe-global/safe-apps-sdk'
+
+export enum EModes {
+ DARK = 'dark',
+ LIGHT = 'light',
+}
+
+type SafeThemeProviderProps = {
+ children: (theme: Theme) => React.ReactNode
+}
+
+export const ThemeModeContext = React.createContext(EModes.LIGHT)
+
+const SafeThemeProvider: FC = ({ children }) => {
+ const [mode, setMode] = useState(EModes.LIGHT)
+
+ const theme = useMemo(() => createSafeTheme(mode), [mode])
+
+ useEffect(() => {
+ window.parent.postMessage(
+ {
+ id: 'tx-builder',
+ env: { sdkVersion: getSDKVersion() },
+ method: 'getCurrentTheme',
+ },
+ '*',
+ )
+
+ window.addEventListener('message', function ({ data: eventData }) {
+ if (!eventData?.data?.hasOwnProperty('darkMode')) return
+
+ setMode(eventData?.data.darkMode ? EModes.DARK : EModes.LIGHT)
+ })
+ }, [])
+
+ return (
+
+ {children(theme)}
+
+ )
+}
+
+export default SafeThemeProvider
diff --git a/apps/tx-builder/src/theme/darkPalette.ts b/apps/tx-builder/src/theme/darkPalette.ts
new file mode 100644
index 000000000..3ed7ebf00
--- /dev/null
+++ b/apps/tx-builder/src/theme/darkPalette.ts
@@ -0,0 +1,71 @@
+const darkPalette = {
+ text: {
+ primary: '#FFFFFF',
+ secondary: '#636669',
+ disabled: '#636669',
+ },
+ primary: {
+ dark: '#0cb259',
+ main: '#12FF80',
+ light: '#A1A3A7',
+ },
+ secondary: {
+ dark: '#636669',
+ main: '#FFFFFF',
+ light: '#B0FFC9',
+ background: '#1B2A22',
+ },
+ border: {
+ main: '#636669',
+ light: '#303033',
+ background: '#121312',
+ },
+ error: {
+ dark: '#AC2C3B',
+ main: '#FF5F72',
+ light: '#FFB4BD',
+ background: '#2F2527',
+ },
+ success: {
+ dark: '#028D4C',
+ main: '#00B460',
+ light: '#81C784',
+ background: '#1F2920',
+ },
+ info: {
+ dark: '#52BFDC',
+ main: '#5FDDFF',
+ light: '#B7F0FF',
+ background: '#19252C',
+ },
+ warning: {
+ dark: '#C04C32',
+ main: '#FF8061',
+ light: '#FFBC9F',
+ background: '#2F2318',
+ },
+ background: {
+ default: '#121312',
+ main: '#121312',
+ paper: '#1C1C1C',
+ light: '#1B2A22',
+ },
+ backdrop: {
+ main: '#636669',
+ },
+ logo: {
+ main: '#FFFFFF',
+ background: '#303033',
+ },
+ upload: {
+ primary: '#fff',
+ },
+ static: {
+ main: '#121312',
+ },
+ code: {
+ main: 'transparent',
+ },
+}
+
+export default darkPalette
diff --git a/apps/tx-builder/src/theme/lightPalette.ts b/apps/tx-builder/src/theme/lightPalette.ts
new file mode 100644
index 000000000..413a65dfd
--- /dev/null
+++ b/apps/tx-builder/src/theme/lightPalette.ts
@@ -0,0 +1,71 @@
+const lightPalette = {
+ text: {
+ primary: '#121312',
+ secondary: '#A1A3A7',
+ disabled: '#DDDEE0',
+ },
+ primary: {
+ dark: '#3c3c3c',
+ main: '#121312',
+ light: '#636669',
+ },
+ secondary: {
+ dark: '#0FDA6D',
+ main: '#12FF80',
+ light: '#B0FFC9',
+ background: '#EFFFF4',
+ },
+ border: {
+ main: '#A1A3A7',
+ light: '#DCDEE0',
+ background: '#F4F4F4',
+ },
+ error: {
+ dark: '#AC2C3B',
+ main: '#FF5F72',
+ light: '#FFB4BD',
+ background: '#FFE6EA',
+ },
+ success: {
+ dark: '#028D4C',
+ main: '#00B460',
+ light: '#72F5B8',
+ background: '#EFFAF1',
+ },
+ info: {
+ dark: '#52BFDC',
+ main: '#5FDDFF',
+ light: '#B7F0FF',
+ background: '#EFFCFF',
+ },
+ warning: {
+ dark: '#C04C32',
+ main: '#FF8061',
+ light: '#FFBC9F',
+ background: '#FFF1E0',
+ },
+ background: {
+ default: '#F4F4F4',
+ main: '#F4F4F4',
+ paper: '#FFFFFF',
+ light: '#EFFFF4',
+ },
+ backdrop: {
+ main: '#636669',
+ },
+ logo: {
+ main: '#121312',
+ background: '#EEEFF0',
+ },
+ upload: {
+ primary: '#12FF80',
+ },
+ static: {
+ main: '#121312',
+ },
+ code: {
+ main: '#FFFFFF',
+ },
+}
+
+export default lightPalette
diff --git a/apps/tx-builder/src/theme/safeTheme.ts b/apps/tx-builder/src/theme/safeTheme.ts
new file mode 100644
index 000000000..799b2860f
--- /dev/null
+++ b/apps/tx-builder/src/theme/safeTheme.ts
@@ -0,0 +1,520 @@
+import type { Theme, PaletteMode } from '@mui/material'
+import { alpha } from '@mui/material'
+import type { Shadows } from '@mui/material/styles'
+import { createTheme } from '@mui/material/styles'
+
+import palette from './lightPalette'
+import darkPalette from './darkPalette'
+import typography from './typography'
+
+export const base = 8
+
+declare module '@mui/material/styles' {
+ // Custom color palettes
+ export interface Palette {
+ border: Palette['primary']
+ logo: Palette['primary']
+ backdrop: Palette['primary']
+ static: Palette['primary']
+ }
+
+ export interface PaletteOptions {
+ border: PaletteOptions['primary']
+ logo: PaletteOptions['primary']
+ backdrop: PaletteOptions['primary']
+ static: PaletteOptions['primary']
+ }
+
+ export interface TypeBackground {
+ main: string
+ light: string
+ }
+
+ // Custom color properties
+ export interface PaletteColor {
+ background?: string
+ }
+
+ export interface SimplePaletteColorOptions {
+ background?: string
+ }
+}
+
+declare module '@mui/material/SvgIcon' {
+ export interface SvgIconPropsColorOverrides {
+ border: unknown
+ }
+}
+
+declare module '@mui/material/Button' {
+ export interface ButtonPropsSizeOverrides {
+ stretched: true
+ }
+
+ export interface ButtonPropsColorOverrides {
+ background: true
+ }
+
+ export interface ButtonPropsVariantOverrides {
+ danger: true
+ }
+}
+
+declare module '@mui/material/IconButton' {
+ export interface IconButtonPropsColorOverrides {
+ border: true
+ }
+}
+
+const createSafeTheme = (mode: PaletteMode): Theme => {
+ const isDarkMode = mode === 'dark'
+ const colors = isDarkMode ? darkPalette : palette
+ const shadowColor = colors.primary.light
+
+ return createTheme({
+ palette: {
+ mode: isDarkMode ? 'dark' : 'light',
+ ...colors,
+ },
+ spacing: base,
+ shape: {
+ borderRadius: 6,
+ },
+ shadows: [
+ 'none',
+ isDarkMode
+ ? `0 0 2px ${shadowColor}`
+ : `0 1px 4px ${shadowColor}0a, 0 4px 10px ${shadowColor}14`,
+ isDarkMode
+ ? `0 0 2px ${shadowColor}`
+ : `0 1px 4px ${shadowColor}0a, 0 4px 10px ${shadowColor}14`,
+ isDarkMode
+ ? `0 0 2px ${shadowColor}`
+ : `0 2px 20px ${shadowColor}0a, 0 8px 32px ${shadowColor}14`,
+ isDarkMode
+ ? `0 0 2px ${shadowColor}`
+ : `0 8px 32px ${shadowColor}0a, 0 24px 60px ${shadowColor}14`,
+ ...Array(20).fill('none'),
+ ] as Shadows,
+ typography,
+ components: {
+ MuiTableCell: {
+ styleOverrides: {
+ head: ({ theme }) => ({
+ ...theme.typography.body1,
+ color: theme.palette.primary.light,
+ }),
+ },
+ },
+ MuiButton: {
+ variants: [
+ {
+ props: { size: 'stretched' },
+ style: {
+ padding: '12px 48px',
+ },
+ },
+ {
+ props: { variant: 'danger' },
+ style: ({ theme }) => ({
+ backgroundColor: theme.palette.error.background,
+ color: theme.palette.error.main,
+ '&:hover': {
+ color: theme.palette.error.dark,
+ backgroundColor: theme.palette.error.light,
+ },
+ }),
+ },
+ ],
+ styleOverrides: {
+ sizeSmall: {
+ fontSize: '14px',
+ padding: '8px 24px',
+ },
+ sizeMedium: {
+ fontSize: '16px',
+ padding: '12px 24px',
+ },
+ root: ({ theme }) => ({
+ borderRadius: theme.shape.borderRadius,
+ fontWeight: 'bold',
+ lineHeight: 1.25,
+ borderColor: theme.palette.primary.main,
+ textTransform: 'none',
+ '&:hover': {
+ boxShadow: 'none',
+ },
+ }),
+ outlined: {
+ border: '2px solid',
+ '&:hover': {
+ border: '2px solid',
+ },
+ },
+ sizeLarge: { fontSize: '16px' },
+ },
+ },
+ MuiAccordion: {
+ variants: [
+ {
+ props: { variant: 'elevation' },
+ style: ({ theme }) => ({
+ border: 'none',
+ boxShadow: '0',
+ '&:not(:last-of-type)': {
+ borderRadius: '0 !important',
+ borderBottom: `1px solid ${theme.palette.border.light}`,
+ },
+ '&:last-of-type': {
+ borderBottomLeftRadius: '8px',
+ },
+ }),
+ },
+ ],
+ styleOverrides: {
+ root: ({ theme }) => ({
+ transition: 'background 0.2s, border 0.2s',
+ borderRadius: theme.shape.borderRadius,
+ border: `1px solid ${theme.palette.border.light}`,
+ overflow: 'hidden',
+
+ '&::before': {
+ content: 'none',
+ },
+
+ '&:hover': {
+ borderColor: theme.palette.secondary.light,
+ },
+
+ '&:hover > .MuiAccordionSummary-root': {
+ background: theme.palette.background.light,
+ },
+
+ '&.Mui-expanded': {
+ margin: 0,
+ borderColor: theme.palette.secondary.light,
+ },
+
+ '&.Mui-expanded > .MuiAccordionSummary-root': {
+ background: theme.palette.background.light,
+ },
+ }),
+ },
+ },
+ MuiAccordionSummary: {
+ styleOverrides: {
+ root: {
+ '&.Mui-expanded': {
+ minHeight: '48px',
+ },
+ },
+ content: {
+ '&.Mui-expanded': {
+ margin: '12px 0',
+ },
+ },
+ },
+ },
+ MuiAccordionDetails: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ padding: theme.spacing(2),
+ }),
+ },
+ },
+ MuiCard: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ borderRadius: theme.shape.borderRadius,
+ boxSizing: 'border-box',
+ border: '2px solid transparent',
+ boxShadow: 'none',
+ }),
+ },
+ },
+ MuiDialog: {
+ defaultProps: {
+ fullWidth: true,
+ },
+ },
+ MuiDialogContent: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ padding: theme.spacing(3),
+ }),
+ },
+ },
+ MuiDivider: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ borderColor: theme.palette.border.light,
+ }),
+ },
+ },
+ MuiPaper: {
+ defaultProps: {
+ elevation: 0,
+ },
+ styleOverrides: {
+ outlined: ({ theme }) => ({
+ borderWidth: 2,
+ borderColor: theme.palette.border.light,
+ }),
+ root: ({ theme }) => ({
+ borderRadius: theme.shape.borderRadius,
+ backgroundImage: 'none',
+ }),
+ },
+ },
+ MuiPopover: {
+ defaultProps: {
+ elevation: 2,
+ },
+ styleOverrides: {
+ paper: {
+ overflow: 'visible',
+ },
+ },
+ },
+ MuiIconButton: {
+ styleOverrides: {
+ sizeSmall: {
+ padding: '4px',
+ },
+ },
+ },
+ MuiToggleButton: {
+ styleOverrides: {
+ root: {
+ textTransform: 'none',
+ },
+ },
+ },
+ MuiChip: {
+ styleOverrides: {
+ colorSuccess: ({ theme }) => ({
+ backgroundColor: theme.palette.secondary.light,
+ height: '24px',
+ }),
+ },
+ },
+ MuiAlert: {
+ styleOverrides: {
+ standardError: ({ theme }) => ({
+ '& .MuiAlert-icon': {
+ color: theme.palette.error.main,
+ },
+ '&.MuiPaper-root': {
+ backgroundColor: theme.palette.error.background,
+ },
+ border: `1px solid ${theme.palette.error.main}`,
+ }),
+ standardInfo: ({ theme }) => ({
+ '& .MuiAlert-icon': {
+ color: theme.palette.info.main,
+ },
+ '&.MuiPaper-root': {
+ backgroundColor: theme.palette.info.background,
+ },
+ border: `1px solid ${theme.palette.info.main}`,
+ }),
+ standardSuccess: ({ theme }) => ({
+ '& .MuiAlert-icon': {
+ color: theme.palette.success.main,
+ },
+ '&.MuiPaper-root': {
+ backgroundColor: theme.palette.success.background,
+ },
+ border: `1px solid ${theme.palette.success.main}`,
+ }),
+ standardWarning: ({ theme }) => ({
+ '& .MuiAlert-icon': {
+ color: theme.palette.warning.main,
+ },
+ '&.MuiPaper-root': {
+ backgroundColor: theme.palette.warning.background,
+ },
+ border: `1px solid ${theme.palette.warning.main}`,
+ }),
+ root: ({ theme }) => ({
+ color: theme.palette.text.primary,
+ padding: '12px 16px',
+ }),
+ },
+ },
+ MuiTableHead: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ '& .MuiTableCell-root': {
+ borderBottom: `1px solid ${theme.palette.border.light}`,
+ },
+
+ [theme.breakpoints.down('sm')]: {
+ '& .MuiTableCell-root:first-of-type': {
+ paddingRight: theme.spacing(1),
+ },
+
+ '& .MuiTableCell-root:not(:first-of-type):not(:last-of-type)': {
+ paddingLeft: theme.spacing(1),
+ paddingRight: theme.spacing(1),
+ },
+
+ '& .MuiTableCell-root:last-of-type': {
+ paddingLeft: theme.spacing(1),
+ },
+ },
+ }),
+ },
+ },
+ MuiTableBody: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ '& .MuiTableCell-root': {
+ paddingTop: theme.spacing(1),
+ paddingBottom: theme.spacing(1),
+ borderBottom: 'none',
+ },
+
+ [theme.breakpoints.down('sm')]: {
+ '& .MuiTableCell-root:first-of-type': {
+ paddingRight: theme.spacing(1),
+ },
+
+ '& .MuiTableCell-root:not(:first-of-type):not(:last-of-type)': {
+ paddingLeft: theme.spacing(1),
+ paddingRight: theme.spacing(1),
+ },
+
+ '& .MuiTableCell-root:last-of-type': {
+ paddingLeft: theme.spacing(1),
+ },
+ },
+
+ '& .MuiTableRow-root': {
+ transition: 'background-color 0.2s',
+ '&:not(:last-of-type)': {
+ borderBottom: `1px solid ${theme.palette.border.light}`,
+ },
+ },
+
+ '& .MuiTableRow-root:hover': {
+ backgroundColor: theme.palette.background.light,
+ },
+ '& .MuiTableRow-root.Mui-selected': {
+ backgroundColor: theme.palette.background.light,
+ },
+ }),
+ },
+ },
+ MuiCheckbox: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ color: theme.palette.primary.main,
+ }),
+ },
+ },
+ MuiOutlinedInput: {
+ styleOverrides: {
+ notchedOutline: ({ theme }) => ({
+ borderColor: theme.palette.border.main,
+ }),
+ root: ({ theme }) => ({
+ borderColor: theme.palette.border.main,
+ }),
+ },
+ },
+ MuiSvgIcon: {
+ styleOverrides: {
+ fontSizeSmall: {
+ width: '1rem',
+ height: '1rem',
+ },
+ },
+ },
+ MuiFilledInput: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ borderRadius: 4,
+ backgroundColor: theme.palette.background.paper,
+ border: '1px solid transparent',
+ transition: 'border-color 0.2s',
+
+ '&:hover, &:focus, &.Mui-focused': {
+ backgroundColor: theme.palette.background.paper,
+ borderColor: theme.palette.primary.main,
+ },
+ }),
+ },
+ },
+ MuiSelect: {
+ defaultProps: {
+ MenuProps: {
+ sx: {
+ '& .MuiPaper-root': {
+ overflow: 'auto',
+ },
+ },
+ },
+ },
+ },
+ MuiTooltip: {
+ styleOverrides: {
+ tooltip: ({ theme }) => ({
+ ...theme.typography.body2,
+ color: theme.palette.background.main,
+ backgroundColor: theme.palette.text.primary,
+ '& .MuiLink-root': {
+ color: isDarkMode ? theme.palette.background.main : theme.palette.secondary.main,
+ textDecorationColor: isDarkMode
+ ? theme.palette.background.main
+ : theme.palette.secondary.main,
+ },
+ '& .MuiLink-root:hover': {
+ color: isDarkMode ? theme.palette.text.secondary : theme.palette.secondary.light,
+ },
+ }),
+ arrow: ({ theme }) => ({
+ color: theme.palette.text.primary,
+ }),
+ },
+ },
+ MuiBackdrop: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ backgroundColor: alpha(theme.palette.backdrop.main, 0.75),
+ }),
+ },
+ },
+ MuiSwitch: {
+ defaultProps: {
+ color: isDarkMode ? undefined : 'success',
+ },
+ styleOverrides: {
+ thumb: () => ({
+ boxShadow:
+ '0px 2px 6px -1px rgba(0, 0, 0, 0.2), 0px 1px 4px rgba(0, 0, 0, 0.14), 0px 1px 4px rgba(0, 0, 0, 0.14)',
+ }),
+ },
+ },
+ MuiLink: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ fontWeight: 700,
+ '&:hover': {
+ color: theme.palette.primary.light,
+ },
+ }),
+ },
+ },
+ MuiLinearProgress: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ backgroundColor: theme.palette.border.light,
+ }),
+ },
+ },
+ },
+ })
+}
+
+export default createSafeTheme
diff --git a/apps/tx-builder/src/theme/typography.ts b/apps/tx-builder/src/theme/typography.ts
new file mode 100644
index 000000000..3058ccd9d
--- /dev/null
+++ b/apps/tx-builder/src/theme/typography.ts
@@ -0,0 +1,50 @@
+import type { TypographyOptions } from '@mui/material/styles/createTypography'
+
+const safeFontFamily = 'DM Sans, sans-serif'
+
+const typography: TypographyOptions = {
+ fontFamily: safeFontFamily,
+ h1: {
+ fontSize: '32px',
+ lineHeight: '36px',
+ fontWeight: 700,
+ },
+ h2: {
+ fontSize: '27px',
+ lineHeight: '34px',
+ fontWeight: 700,
+ },
+ h3: {
+ fontSize: '24px',
+ lineHeight: '30px',
+ },
+ h4: {
+ fontSize: '20px',
+ lineHeight: '26px',
+ },
+ h5: {
+ fontSize: '16px',
+ fontWeight: 700,
+ },
+ body1: {
+ fontSize: '16px',
+ lineHeight: '22px',
+ },
+ body2: {
+ fontSize: '14px',
+ lineHeight: '20px',
+ },
+ caption: {
+ fontSize: '12px',
+ lineHeight: '16px',
+ letterSpacing: '0.4px',
+ },
+ overline: {
+ fontSize: '11px',
+ lineHeight: '14px',
+ textTransform: 'uppercase',
+ letterSpacing: '1px',
+ },
+}
+
+export default typography
diff --git a/apps/tx-builder/src/utils/address.ts b/apps/tx-builder/src/utils/address.ts
new file mode 100644
index 000000000..d78743c2b
--- /dev/null
+++ b/apps/tx-builder/src/utils/address.ts
@@ -0,0 +1,59 @@
+import { checkAddressChecksum, toChecksumAddress, isAddress, isHexStrict } from 'web3-utils'
+
+const getAddressWithoutNetworkPrefix = (address = ''): string => {
+ const hasPrefix = address.includes(':')
+
+ if (!hasPrefix) {
+ return address
+ }
+
+ const [, ...addressWithoutNetworkPrefix] = address.split(':')
+
+ return addressWithoutNetworkPrefix.join('')
+}
+
+const getNetworkPrefix = (address = ''): string => {
+ const splitAddress = address.split(':')
+ const hasPrefixDefined = splitAddress.length > 1
+ const [prefix] = splitAddress
+ return hasPrefixDefined ? prefix : ''
+}
+
+const addNetworkPrefix = (address: string, prefix: string | undefined): string => {
+ return !!prefix ? `${prefix}:${address}` : address
+}
+
+const checksumAddress = (address: string): string => toChecksumAddress(address)
+
+const isChecksumAddress = (address?: string): boolean => {
+ if (address) {
+ return checkAddressChecksum(address)
+ }
+
+ return false
+}
+
+const isValidAddress = (address?: string): boolean => {
+ if (address) {
+ // `isAddress` do not require the string to start with `0x`
+ // `isHexStrict` ensures the address to start with `0x` aside from being a valid hex string
+ return isHexStrict(address) && isAddress(address)
+ }
+
+ return false
+}
+
+// Based on https://docs.ens.domains/dapp-developer-guide/resolving-names
+// [...] a correct integration of ENS treats any dot-separated name as a potential ENS name [...]
+const validENSRegex = new RegExp(/[^[\]]+\.[^[\]]/)
+const isValidEnsName = (name: string): boolean => validENSRegex.test(name)
+
+export {
+ getAddressWithoutNetworkPrefix,
+ getNetworkPrefix,
+ addNetworkPrefix,
+ checksumAddress,
+ isChecksumAddress,
+ isValidAddress,
+ isValidEnsName,
+}
diff --git a/apps/tx-builder/src/utils/strings.ts b/apps/tx-builder/src/utils/strings.ts
new file mode 100644
index 000000000..2cebc15e3
--- /dev/null
+++ b/apps/tx-builder/src/utils/strings.ts
@@ -0,0 +1,25 @@
+export const textShortener = (
+ text: string,
+ charsStart: number,
+ charsEnd: number,
+ separator = '...',
+): string => {
+ const amountOfCharsToKeep = charsEnd + charsStart
+
+ if (amountOfCharsToKeep >= text.length || !amountOfCharsToKeep) {
+ // no need to shorten
+ return text
+ }
+
+ const r = new RegExp(`^(.{${charsStart}}).+(.{${charsEnd}})$`)
+ const matchResult = r.exec(text)
+
+ if (!matchResult) {
+ // if for any reason the exec returns null, the text remains untouched
+ return text
+ }
+
+ const [, textStart, textEnd] = matchResult
+
+ return `${textStart}${separator}${textEnd}`
+}
diff --git a/yarn.lock b/yarn.lock
index f98bdd48a..f37a9a315 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2922,12 +2922,17 @@
"@safe-global/safe-gateway-typescript-sdk" "^3.5.3"
viem "^1.0.0"
-"@safe-global/safe-deployments@^1.26.0":
- version "1.26.0"
- resolved "https://registry.yarnpkg.com/@safe-global/safe-deployments/-/safe-deployments-1.26.0.tgz#b83615b3b5a66e736e08f8ecf2801ed988e9e007"
- integrity sha512-Tw89O4/paT19ieMoiWQbqRApb0Bef/DxweS9rxodXAM5EQModkbyFXGZca+YxXE67sLvWjLr2jJUOxwze8mhGw==
+"@safe-global/safe-deployments@^1.27.0":
+ version "1.37.10"
+ resolved "https://registry.yarnpkg.com/@safe-global/safe-deployments/-/safe-deployments-1.37.10.tgz#2f61a25bd479332821ba2e91a575237d77406ec3"
+ integrity sha512-lcxX9CV+xdcLs4dF6Cx18zDww5JyqaX6RdcvU0o/34IgJ4Wjo3J/RNzJAoMhurCAfTGr+0vyJ9V13Qo50AR6JA==
dependencies:
- semver "^7.3.7"
+ semver "^7.6.2"
+
+"@safe-global/safe-gateway-typescript-sdk@^3.19.0":
+ version "3.22.2"
+ resolved "https://registry.yarnpkg.com/@safe-global/safe-gateway-typescript-sdk/-/safe-gateway-typescript-sdk-3.22.2.tgz#d4ff9972e58f9344fc95f8d41b2ec6517baa8e79"
+ integrity sha512-Y0yAxRaB98LFp2Dm+ACZqBSdAmI3FlpH/LjxOZ94g/ouuDJecSq0iR26XZ5QDuEL8Rf+L4jBJaoDC08CD0KkJw==
"@safe-global/safe-gateway-typescript-sdk@^3.5.3", "@safe-global/safe-gateway-typescript-sdk@^3.7.3":
version "3.7.3"
@@ -13928,6 +13933,11 @@ semver@^7.1.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7:
dependencies:
lru-cache "^6.0.0"
+semver@^7.6.2:
+ version "7.6.3"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
+ integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
+
send@0.17.2:
version "0.17.2"
resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820"