Commit b704bdac authored by Jordan Frankfurt's avatar Jordan Frankfurt Committed by GitHub

feat(compliance): risk screening (#3714)

* feat(compliance): risk screening

* add api endpoint

* hosted app only

* add help center link and click-to-copy email address

* only show on app.uniswap.org and fix spacing nits

* 12px for bottom section
parent 00d3df95
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import React from 'react' import useCopyClipboard from 'hooks/useCopyClipboard'
import React, { useCallback } from 'react'
import { CheckCircle, Copy } from 'react-feather' import { CheckCircle, Copy } from 'react-feather'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { LinkStyledButton } from 'theme'
import useCopyClipboard from '../../hooks/useCopyClipboard'
import { LinkStyledButton } from '../../theme'
const CopyIcon = styled(LinkStyledButton)` const CopyIcon = styled(LinkStyledButton)`
color: ${({ theme }) => theme.text3}; color: ${({ color, theme }) => color || theme.text3};
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
text-decoration: none; text-decoration: none;
font-size: 0.825rem; font-size: 12px;
:hover, :hover,
:active, :active,
:focus { :focus {
text-decoration: none; text-decoration: none;
color: ${({ theme }) => theme.text2}; color: ${({ color, theme }) => color || theme.text2};
} }
` `
const TransactionStatusText = styled.span` const TransactionStatusText = styled.span`
margin-left: 0.25rem; margin-left: 0.25rem;
font-size: 0.825rem; font-size: 12px;
${({ theme }) => theme.flexRowNoWrap}; ${({ theme }) => theme.flexRowNoWrap};
align-items: center; align-items: center;
` `
export default function CopyHelper(props: { toCopy: string; children?: React.ReactNode }) { interface BaseProps {
toCopy: string
color?: string
}
export type CopyHelperProps = BaseProps & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, keyof BaseProps>
export default function CopyHelper({ color, toCopy, children }: CopyHelperProps) {
const [isCopied, setCopied] = useCopyClipboard() const [isCopied, setCopied] = useCopyClipboard()
const copy = useCallback(() => {
setCopied(toCopy)
}, [toCopy, setCopied])
return ( return (
<CopyIcon onClick={() => setCopied(props.toCopy)}> <CopyIcon onClick={copy} color={color}>
{isCopied ? ( {isCopied ? (
<TransactionStatusText> <TransactionStatusText>
<CheckCircle size={'16'} /> <CheckCircle size={'12'} />
<TransactionStatusText> <TransactionStatusText>
<Trans>Copied</Trans> <Trans>Copied</Trans>
</TransactionStatusText> </TransactionStatusText>
</TransactionStatusText> </TransactionStatusText>
) : ( ) : (
<TransactionStatusText> <TransactionStatusText>
<Copy size={'16'} /> <Copy size={'12'} />
</TransactionStatusText> </TransactionStatusText>
)} )}
{isCopied ? '' : props.children} &nbsp;
{isCopied ? '' : children}
</CopyIcon> </CopyIcon>
) )
} }
import { Trans } from '@lingui/macro'
import CopyHelper from 'components/AccountDetails/Copy'
import Column from 'components/Column'
import useTheme from 'hooks/useTheme'
import { AlertOctagon } from 'react-feather'
import styled from 'styled-components/macro'
import { ExternalLink, ThemedText } from 'theme'
import Modal from '../Modal'
const ContentWrapper = styled(Column)`
align-items: center;
margin: 32px;
text-align: center;
`
const WarningIcon = styled(AlertOctagon)`
min-height: 22px;
min-width: 22px;
color: ${({ theme }) => theme.warning};
`
interface ConnectedAccountBlockedProps {
account: string | null | undefined
isOpen: boolean
}
export default function ConnectedAccountBlocked(props: ConnectedAccountBlockedProps) {
const theme = useTheme()
return (
<Modal isOpen={props.isOpen} onDismiss={Function.prototype()}>
<ContentWrapper>
<WarningIcon />
<ThemedText.LargeHeader lineHeight={2} marginBottom={1} marginTop={1}>
<Trans>Blocked Address</Trans>
</ThemedText.LargeHeader>
<ThemedText.DarkGray fontSize={12} marginBottom={12}>
{props.account}
</ThemedText.DarkGray>
<ThemedText.Main fontSize={14} marginBottom={12}>
<Trans>This address is blocked on the Uniswap Labs interface because it is associated with one or more</Trans>{' '}
<ExternalLink href="https://help.uniswap.org/en/articles/6149816">
<Trans>blocked activities</Trans>
</ExternalLink>
.
</ThemedText.Main>
<ThemedText.Main fontSize={12}>
<Trans>If you believe this is an error, please email: </Trans>{' '}
</ThemedText.Main>
<CopyHelper toCopy="compliance@uniswap.org" color={theme.primary1}>
compliance@uniswap.org.
</CopyHelper>
</ContentWrapper>
</Modal>
)
}
import AddressClaimModal from 'components/claim/AddressClaimModal'
import ConnectedAccountBlocked from 'components/ConnectedAccountBlocked'
import useAccountRiskCheck from 'hooks/useAccountRiskCheck'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useModalOpen, useToggleModal } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer'
export default function TopLevelModals() {
const addressClaimOpen = useModalOpen(ApplicationModal.ADDRESS_CLAIM)
const addressClaimToggle = useToggleModal(ApplicationModal.ADDRESS_CLAIM)
const blockedAccountModalOpen = useModalOpen(ApplicationModal.BLOCKED_ACCOUNT)
const { account } = useActiveWeb3React()
useAccountRiskCheck(account)
return (
<>
<AddressClaimModal isOpen={addressClaimOpen} onDismiss={addressClaimToggle} />
<ConnectedAccountBlocked account={account} isOpen={Boolean(blockedAccountModalOpen && account)} />
</>
)
}
import { useEffect } from 'react'
import { ApplicationModal, setOpenModal } from 'state/application/reducer'
import { useAppDispatch } from 'state/hooks'
export default function useAccountRiskCheck(account: string | null | undefined) {
const dispatch = useAppDispatch()
useEffect(() => {
if (account && window.location.hostname === 'app.uniswap.org') {
const headers = new Headers({ 'Content-Type': 'application/json' })
fetch('https://screening-worker.uniswap.workers.dev', {
method: 'POST',
headers,
body: JSON.stringify({ address: account }),
})
.then((res) => res.json())
.then((data) => {
if (data.block) {
dispatch(setOpenModal(ApplicationModal.BLOCKED_ACCOUNT))
}
})
.catch(() => {
dispatch(setOpenModal(null))
})
}
}, [account, dispatch])
}
import Loader from 'components/Loader' import Loader from 'components/Loader'
import TopLevelModals from 'components/TopLevelModals'
import ApeModeQueryParamReader from 'hooks/useApeModeQueryParamReader' import ApeModeQueryParamReader from 'hooks/useApeModeQueryParamReader'
import { lazy, Suspense } from 'react' import { lazy, Suspense } from 'react'
import { Redirect, Route, Switch } from 'react-router-dom' import { Redirect, Route, Switch } from 'react-router-dom'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import GoogleAnalyticsReporter from '../components/analytics/GoogleAnalyticsReporter' import GoogleAnalyticsReporter from '../components/analytics/GoogleAnalyticsReporter'
import AddressClaimModal from '../components/claim/AddressClaimModal'
import ErrorBoundary from '../components/ErrorBoundary' import ErrorBoundary from '../components/ErrorBoundary'
import Header from '../components/Header' import Header from '../components/Header'
import Polling from '../components/Header/Polling' import Polling from '../components/Header/Polling'
import Popups from '../components/Popups' import Popups from '../components/Popups'
import Web3ReactManager from '../components/Web3ReactManager' import Web3ReactManager from '../components/Web3ReactManager'
import { useModalOpen, useToggleModal } from '../state/application/hooks'
import { ApplicationModal } from '../state/application/reducer'
import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader' import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader'
import AddLiquidity from './AddLiquidity' import AddLiquidity from './AddLiquidity'
import { RedirectDuplicateTokenIds } from './AddLiquidity/redirects' import { RedirectDuplicateTokenIds } from './AddLiquidity/redirects'
...@@ -65,12 +63,6 @@ const Marginer = styled.div` ...@@ -65,12 +63,6 @@ const Marginer = styled.div`
margin-top: 5rem; margin-top: 5rem;
` `
function TopLevelModals() {
const open = useModalOpen(ApplicationModal.ADDRESS_CLAIM)
const toggle = useToggleModal(ApplicationModal.ADDRESS_CLAIM)
return <AddressClaimModal isOpen={open} onDismiss={toggle} />
}
export default function App() { export default function App() {
return ( return (
<ErrorBoundary> <ErrorBoundary>
......
...@@ -14,17 +14,18 @@ export type PopupContent = ...@@ -14,17 +14,18 @@ export type PopupContent =
} }
export enum ApplicationModal { export enum ApplicationModal {
WALLET,
SETTINGS,
SELF_CLAIM,
ADDRESS_CLAIM, ADDRESS_CLAIM,
BLOCKED_ACCOUNT,
DELEGATE,
CLAIM_POPUP, CLAIM_POPUP,
MENU, MENU,
DELEGATE,
VOTE,
POOL_OVERVIEW_OPTIONS,
NETWORK_SELECTOR, NETWORK_SELECTOR,
POOL_OVERVIEW_OPTIONS,
PRIVACY_POLICY, PRIVACY_POLICY,
SELF_CLAIM,
SETTINGS,
VOTE,
WALLET,
} }
type PopupList = Array<{ key: string; show: boolean; content: PopupContent; removeAfterMs: number | null }> type PopupList = Array<{ key: string; show: boolean; content: PopupContent; removeAfterMs: number | null }>
......
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