Commit 60d35b46 authored by Zach Pomerantz's avatar Zach Pomerantz Committed by GitHub

fix: simplify validation (#3665)

* fix: simplify widget validation

* test: update cosmos to trigger edge cases

* fix: simplify swap validation
parent 3d422cf7
import { SUPPORTED_LOCALES } from 'constants/locales'
import { WidgetProps } from 'lib/components/Widget'
import { IntegrationError } from 'lib/errors'
import { PropsWithChildren, useEffect } from 'react'
export default function WidgetsPropsValidator(props: PropsWithChildren<WidgetProps>) {
const { jsonRpcEndpoint, provider } = props
useEffect(() => {
if (!provider && !jsonRpcEndpoint) {
throw new IntegrationError('This widget requires a provider or jsonRpcEndpoint.')
}
}, [provider, jsonRpcEndpoint])
const { width } = props
useEffect(() => {
if (width && width < 300) {
throw new IntegrationError(`Set widget width to at least 300px. (You set it to ${width}.)`)
}
}, [width])
const { locale } = props
useEffect(() => {
if (locale && locale !== 'pseudo' && !SUPPORTED_LOCALES.includes(locale)) {
console.warn('Unsupported locale: ', locale)
}
}, [locale])
return null
}
...@@ -23,8 +23,8 @@ import ReverseButton from './ReverseButton' ...@@ -23,8 +23,8 @@ import ReverseButton from './ReverseButton'
import Settings from './Settings' import Settings from './Settings'
import { StatusDialog } from './Status' import { StatusDialog } from './Status'
import SwapButton from './SwapButton' import SwapButton from './SwapButton'
import SwapPropValidator from './SwapPropValidator'
import Toolbar from './Toolbar' import Toolbar from './Toolbar'
import useValidate from './useValidate'
function getTransactionFromMap( function getTransactionFromMap(
txs: { [hash: string]: Transaction }, txs: { [hash: string]: Transaction },
...@@ -56,6 +56,8 @@ function Updaters(props: SwapProps & { disabled: boolean }) { ...@@ -56,6 +56,8 @@ function Updaters(props: SwapProps & { disabled: boolean }) {
} }
export default function Swap(props: SwapProps) { export default function Swap(props: SwapProps) {
useValidate(props)
const { active, account } = useActiveWeb3React() const { active, account } = useActiveWeb3React()
const [wrapper, setWrapper] = useState<HTMLDivElement | null>(null) const [wrapper, setWrapper] = useState<HTMLDivElement | null>(null)
...@@ -70,7 +72,6 @@ export default function Swap(props: SwapProps) { ...@@ -70,7 +72,6 @@ export default function Swap(props: SwapProps) {
return ( return (
<> <>
<SwapPropValidator {...props} />
<Updaters {...props} disabled={isDisabled} /> <Updaters {...props} disabled={isDisabled} />
<Header title={<Trans>Swap</Trans>}> <Header title={<Trans>Swap</Trans>}>
{active && <Wallet disabled={!account} onClick={props.onConnectWallet} />} {active && <Wallet disabled={!account} onClick={props.onConnectWallet} />}
......
...@@ -18,12 +18,12 @@ function isAddressOrAddressMap(addressOrMap: DefaultAddress): boolean { ...@@ -18,12 +18,12 @@ function isAddressOrAddressMap(addressOrMap: DefaultAddress): boolean {
type ValidatorProps = PropsWithChildren<TokenDefaults & FeeOptions> type ValidatorProps = PropsWithChildren<TokenDefaults & FeeOptions>
export default function SwapPropValidator(props: ValidatorProps) { export default function useValidate(props: ValidatorProps) {
const { convenienceFee, convenienceFeeRecipient } = props const { convenienceFee, convenienceFeeRecipient } = props
useEffect(() => { useEffect(() => {
if (convenienceFee) { if (convenienceFee) {
if (convenienceFee > 100 || convenienceFee < 0) { if (convenienceFee > 100 || convenienceFee < 0) {
throw new IntegrationError(`convenienceFee must be between 0 and 100. (You set it to ${convenienceFee})`) throw new IntegrationError(`convenienceFee must be between 0 and 100 (you set it to ${convenienceFee}).`)
} }
if (!convenienceFeeRecipient) { if (!convenienceFeeRecipient) {
throw new IntegrationError('convenienceFeeRecipient is required when convenienceFee is set.') throw new IntegrationError('convenienceFeeRecipient is required when convenienceFee is set.')
...@@ -32,7 +32,7 @@ export default function SwapPropValidator(props: ValidatorProps) { ...@@ -32,7 +32,7 @@ export default function SwapPropValidator(props: ValidatorProps) {
if (typeof convenienceFeeRecipient === 'string') { if (typeof convenienceFeeRecipient === 'string') {
if (!isAddress(convenienceFeeRecipient)) { if (!isAddress(convenienceFeeRecipient)) {
throw new IntegrationError( throw new IntegrationError(
`convenienceFeeRecipient must be a valid address. (You set it to ${convenienceFeeRecipient}.)` `convenienceFeeRecipient must be a valid address (you set it to ${convenienceFeeRecipient}).`
) )
} }
} else if (typeof convenienceFeeRecipient === 'object') { } else if (typeof convenienceFeeRecipient === 'object') {
...@@ -40,7 +40,7 @@ export default function SwapPropValidator(props: ValidatorProps) { ...@@ -40,7 +40,7 @@ export default function SwapPropValidator(props: ValidatorProps) {
if (!isAddress(recipient)) { if (!isAddress(recipient)) {
const values = Object.values(convenienceFeeRecipient).join(', ') const values = Object.values(convenienceFeeRecipient).join(', ')
throw new IntegrationError( throw new IntegrationError(
`All values in convenienceFeeRecipient object must be valid addresses. (You used ${values}.)` `All values in convenienceFeeRecipient object must be valid addresses (you used ${values}).`
) )
} }
}) })
...@@ -54,11 +54,11 @@ export default function SwapPropValidator(props: ValidatorProps) { ...@@ -54,11 +54,11 @@ export default function SwapPropValidator(props: ValidatorProps) {
throw new IntegrationError('defaultInputAmount and defaultOutputAmount may not both be defined.') throw new IntegrationError('defaultInputAmount and defaultOutputAmount may not both be defined.')
} }
if (defaultInputAmount && BigNumber.from(defaultInputAmount).lt(0)) { if (defaultInputAmount && BigNumber.from(defaultInputAmount).lt(0)) {
throw new IntegrationError(`defaultInputAmount must be a positive number. (You set it to ${defaultInputAmount})`) throw new IntegrationError(`defaultInputAmount must be a positive number (you set it to ${defaultInputAmount})`)
} }
if (defaultOutputAmount && BigNumber.from(defaultOutputAmount).lt(0)) { if (defaultOutputAmount && BigNumber.from(defaultOutputAmount).lt(0)) {
throw new IntegrationError( throw new IntegrationError(
`defaultOutputAmount must be a positive number. (You set it to ${defaultOutputAmount})` `defaultOutputAmount must be a positive number (you set it to ${defaultOutputAmount}).`
) )
} }
if ( if (
...@@ -67,7 +67,7 @@ export default function SwapPropValidator(props: ValidatorProps) { ...@@ -67,7 +67,7 @@ export default function SwapPropValidator(props: ValidatorProps) {
defaultInputTokenAddress !== 'NATIVE' defaultInputTokenAddress !== 'NATIVE'
) { ) {
throw new IntegrationError( throw new IntegrationError(
`defaultInputTokenAddress(es) must be a valid address or "NATIVE". (You set it to ${defaultInputTokenAddress}` `defaultInputTokenAddress must be a valid address or "NATIVE" (you set it to ${defaultInputTokenAddress}).`
) )
} }
if ( if (
...@@ -76,10 +76,8 @@ export default function SwapPropValidator(props: ValidatorProps) { ...@@ -76,10 +76,8 @@ export default function SwapPropValidator(props: ValidatorProps) {
defaultOutputTokenAddress !== 'NATIVE' defaultOutputTokenAddress !== 'NATIVE'
) { ) {
throw new IntegrationError( throw new IntegrationError(
`defaultOutputTokenAddress(es) must be a valid address or "NATIVE". (You set it to ${defaultOutputTokenAddress}` `defaultOutputTokenAddress must be a valid address or "NATIVE" (you set it to ${defaultOutputTokenAddress}).`
) )
} }
}, [defaultInputTokenAddress, defaultInputAmount, defaultOutputTokenAddress, defaultOutputAmount]) }, [defaultInputTokenAddress, defaultInputAmount, defaultOutputTokenAddress, defaultOutputAmount])
return null
} }
import { JsonRpcProvider } from '@ethersproject/providers' import { JsonRpcProvider } from '@ethersproject/providers'
import { Provider as Eip1193Provider } from '@web3-react/types' import { Provider as Eip1193Provider } from '@web3-react/types'
import { DEFAULT_LOCALE, SupportedLocale } from 'constants/locales' import { DEFAULT_LOCALE, SUPPORTED_LOCALES, SupportedLocale } from 'constants/locales'
import { Provider as AtomProvider } from 'jotai' import { Provider as AtomProvider } from 'jotai'
import { TransactionsUpdater } from 'lib/hooks/transactions' import { TransactionsUpdater } from 'lib/hooks/transactions'
import { ActiveWeb3Provider } from 'lib/hooks/useActiveWeb3React' import { ActiveWeb3Provider } from 'lib/hooks/useActiveWeb3React'
...@@ -9,12 +9,11 @@ import { Provider as I18nProvider } from 'lib/i18n' ...@@ -9,12 +9,11 @@ import { Provider as I18nProvider } from 'lib/i18n'
import { MulticallUpdater, store as multicallStore } from 'lib/state/multicall' import { MulticallUpdater, store as multicallStore } from 'lib/state/multicall'
import styled, { keyframes, Theme, ThemeProvider } from 'lib/theme' import styled, { keyframes, Theme, ThemeProvider } from 'lib/theme'
import { UNMOUNTING } from 'lib/utils/animations' import { UNMOUNTING } from 'lib/utils/animations'
import { PropsWithChildren, StrictMode, useState } from 'react' import { PropsWithChildren, StrictMode, useMemo, useState } from 'react'
import { Provider as ReduxProvider } from 'react-redux' import { Provider as ReduxProvider } from 'react-redux'
import { Modal, Provider as DialogProvider } from './Dialog' import { Modal, Provider as DialogProvider } from './Dialog'
import ErrorBoundary, { ErrorHandler } from './Error/ErrorBoundary' import ErrorBoundary, { ErrorHandler } from './Error/ErrorBoundary'
import WidgetPropValidator from './Error/WidgetsPropsValidator'
const WidgetWrapper = styled.div<{ width?: number | string }>` const WidgetWrapper = styled.div<{ width?: number | string }>`
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
...@@ -103,17 +102,22 @@ export type WidgetProps = { ...@@ -103,17 +102,22 @@ export type WidgetProps = {
} }
export default function Widget(props: PropsWithChildren<WidgetProps>) { export default function Widget(props: PropsWithChildren<WidgetProps>) {
const { const { children, theme, provider, jsonRpcEndpoint, dialog: userDialog, className, onError } = props
children, const width = useMemo(() => {
theme, if (props.width && props.width < 300) {
locale = DEFAULT_LOCALE, console.warn(`Widget width must be at least 300px (you set it to ${props.width}). Falling back to 300px.`)
provider, return 300
jsonRpcEndpoint, }
width = 360, return props.width ?? 360
dialog: userDialog, }, [props.width])
className, const locale = useMemo(() => {
onError, if (props.locale && ![...SUPPORTED_LOCALES, 'pseudo'].includes(props.locale)) {
} = props console.warn(`Unsupported locale: ${props.locale}. Falling back to ${DEFAULT_LOCALE}.`)
return DEFAULT_LOCALE
}
return props.locale ?? DEFAULT_LOCALE
}, [props.locale])
const [dialog, setDialog] = useState<HTMLDivElement | null>(null) const [dialog, setDialog] = useState<HTMLDivElement | null>(null)
return ( return (
<StrictMode> <StrictMode>
...@@ -123,7 +127,6 @@ export default function Widget(props: PropsWithChildren<WidgetProps>) { ...@@ -123,7 +127,6 @@ export default function Widget(props: PropsWithChildren<WidgetProps>) {
<DialogWrapper ref={setDialog} /> <DialogWrapper ref={setDialog} />
<DialogProvider value={userDialog || dialog}> <DialogProvider value={userDialog || dialog}>
<ErrorBoundary onError={onError}> <ErrorBoundary onError={onError}>
<WidgetPropValidator {...props} />
<ReduxProvider store={multicallStore}> <ReduxProvider store={multicallStore}>
<AtomProvider> <AtomProvider>
<ActiveWeb3Provider provider={provider} jsonRpcEndpoint={jsonRpcEndpoint}> <ActiveWeb3Provider provider={provider} jsonRpcEndpoint={jsonRpcEndpoint}>
......
...@@ -17,14 +17,15 @@ const [walletConnect] = initializeConnector<WalletConnect>( ...@@ -17,14 +17,15 @@ const [walletConnect] = initializeConnector<WalletConnect>(
export default function Wrapper({ children }: { children: ReactNode }) { export default function Wrapper({ children }: { children: ReactNode }) {
const [width] = useValue('width', { defaultValue: 360 }) const [width] = useValue('width', { defaultValue: 360 })
const [locale] = useSelect('locale', { defaultValue: DEFAULT_LOCALE, options: ['pseudo', ...SUPPORTED_LOCALES] }) const [locale] = useSelect('locale', {
defaultValue: DEFAULT_LOCALE,
options: ['fa-KE (unsupported)', 'pseudo', ...SUPPORTED_LOCALES],
})
const [darkMode] = useValue('dark mode', { defaultValue: false }) const [darkMode] = useValue('dark mode', { defaultValue: false })
const [theme, setTheme] = useValue('theme', { defaultValue: { ...defaultTheme, ...lightTheme } }) const [theme, setTheme] = useValue('theme', { defaultValue: { ...defaultTheme, ...lightTheme } })
useEffect(() => { useEffect(() => {
setTheme({ ...defaultTheme, ...(darkMode ? darkTheme : lightTheme) }) setTheme({ ...defaultTheme, ...(darkMode ? darkTheme : lightTheme) })
// cosmos does not maintain referential equality for setters }, [darkMode, setTheme])
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [darkMode])
const NO_JSON_RPC = 'None' const NO_JSON_RPC = 'None'
const [jsonRpcEndpoint] = useSelect('JSON-RPC', { const [jsonRpcEndpoint] = useSelect('JSON-RPC', {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment