feat(expert mode): Add expert mode (#828)
* Add expert mode scaffolding * move advanced settings to settings tab, add expert mode * update settings modal * update font weight * fix text * update with modal ; * add null checks * update with input checking * merge and add fixes * update language and bg Co-authored-by: ianlapham <ianlapham@gmail.com>
This commit is contained in:
parent
e20936709c
commit
fd162a72ff
@ -244,6 +244,9 @@ const ButtonErrorStyle = styled(Base)`
|
||||
&:disabled {
|
||||
opacity: 50%;
|
||||
cursor: auto;
|
||||
box-shadow: none;
|
||||
background-color: ${({ theme }) => theme.red1};
|
||||
border: 1px solid ${({ theme }) => theme.red1};
|
||||
}
|
||||
`
|
||||
|
||||
|
@ -17,6 +17,7 @@ import { useTokenBalanceTreatingWETHasETH } from '../../state/wallet/hooks'
|
||||
import { ExternalLink, StyledInternalLink } from '../../theme'
|
||||
import { YellowCard } from '../Card'
|
||||
import { AutoColumn } from '../Column'
|
||||
import Settings from '../Settings'
|
||||
import Menu from '../Menu'
|
||||
|
||||
import Row, { RowBetween } from '../Row'
|
||||
@ -31,15 +32,12 @@ const HeaderFrame = styled.div`
|
||||
width: 100%;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
|
||||
pointer-events: none;
|
||||
|
||||
z-index: 2;
|
||||
${({ theme }) => theme.mediaWidth.upToExtraSmall`
|
||||
padding: 12px 0 0 0;
|
||||
width: calc(100%);
|
||||
position: relative;
|
||||
`};
|
||||
z-index: 2;
|
||||
`
|
||||
|
||||
const HeaderElement = styled.div`
|
||||
@ -130,6 +128,12 @@ const NETWORK_LABELS: { [chainId in ChainId]: string | null } = {
|
||||
[ChainId.KOVAN]: 'Kovan'
|
||||
}
|
||||
|
||||
const BalanceWrapper = styled.div`
|
||||
${({ theme }) => theme.mediaWidth.upToExtraSmall`
|
||||
display: none;
|
||||
`};
|
||||
`
|
||||
|
||||
export default function Header() {
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
|
||||
@ -174,16 +178,17 @@ export default function Header() {
|
||||
{!isMobile && NETWORK_LABELS[chainId] && <NetworkCard>{NETWORK_LABELS[chainId]}</NetworkCard>}
|
||||
</TestnetWrapper>
|
||||
<AccountElement active={!!account} style={{ pointerEvents: 'auto' }}>
|
||||
{account && userEthBalance ? (
|
||||
<Text style={{ flexShrink: 0 }} pl="0.75rem" pr="0.5rem" fontWeight={500}>
|
||||
{userEthBalance?.toSignificant(4)} ETH
|
||||
</Text>
|
||||
) : null}
|
||||
<BalanceWrapper>
|
||||
{account && userEthBalance ? (
|
||||
<Text style={{ flexShrink: 0 }} pl="0.75rem" pr="0.5rem" fontWeight={500}>
|
||||
{userEthBalance?.toSignificant(4)} ETH
|
||||
</Text>
|
||||
) : null}
|
||||
</BalanceWrapper>
|
||||
<Web3Status />
|
||||
</AccountElement>
|
||||
<div style={{ pointerEvents: 'auto' }}>
|
||||
<Menu />
|
||||
</div>
|
||||
<Settings />
|
||||
<Menu />
|
||||
</HeaderElement>
|
||||
</RowBetween>
|
||||
</HeaderFrame>
|
||||
|
253
src/components/Settings/index.tsx
Normal file
253
src/components/Settings/index.tsx
Normal file
@ -0,0 +1,253 @@
|
||||
import React, { useRef, useEffect, useContext, useState } from 'react'
|
||||
import { Settings, X } from 'react-feather'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import {
|
||||
useUserSlippageTolerance,
|
||||
useExpertModeManager,
|
||||
useUserDeadline,
|
||||
useDarkModeManager
|
||||
} from '../../state/user/hooks'
|
||||
import SlippageTabs from '../SlippageTabs'
|
||||
import { RowFixed, RowBetween } from '../Row'
|
||||
import { TYPE } from '../../theme'
|
||||
import QuestionHelper from '../QuestionHelper'
|
||||
import Toggle from '../Toggle'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { ButtonError } from '../Button'
|
||||
import { useSettingsMenuOpen, useToggleSettingsMenu } from '../../state/application/hooks'
|
||||
import { Text } from 'rebass'
|
||||
import Modal from '../Modal'
|
||||
|
||||
const StyledMenuIcon = styled(Settings)`
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
|
||||
> * {
|
||||
stroke: ${({ theme }) => theme.text1};
|
||||
}
|
||||
`
|
||||
|
||||
const StyledCloseIcon = styled(X)`
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
> * {
|
||||
stroke: ${({ theme }) => theme.text1};
|
||||
}
|
||||
`
|
||||
|
||||
const StyledMenuButton = styled.button`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 35px;
|
||||
background-color: ${({ theme }) => theme.bg3};
|
||||
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
:hover,
|
||||
:focus {
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
background-color: ${({ theme }) => theme.bg4};
|
||||
}
|
||||
|
||||
svg {
|
||||
margin-top: 2px;
|
||||
}
|
||||
`
|
||||
const EmojiWrapper = styled.div`
|
||||
position: absolute;
|
||||
bottom: -6px;
|
||||
right: 0px;
|
||||
font-size: 14px;
|
||||
`
|
||||
|
||||
const StyledMenu = styled.div`
|
||||
margin-left: 0.5rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
border: none;
|
||||
text-align: left;
|
||||
`
|
||||
|
||||
const MenuFlyout = styled.span`
|
||||
min-width: 20.125rem;
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
|
||||
0px 24px 32px rgba(0, 0, 0, 0.01);
|
||||
border-radius: 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 1rem;
|
||||
position: absolute;
|
||||
top: 3rem;
|
||||
right: 0rem;
|
||||
z-index: 100;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToExtraSmall`
|
||||
min-width: 18.125rem;
|
||||
right: -46px;
|
||||
`};
|
||||
`
|
||||
|
||||
const Break = styled.div`
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: ${({ theme }) => theme.bg3};
|
||||
`
|
||||
|
||||
const ModalContentWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem 0;
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
border-radius: 20px;
|
||||
`
|
||||
|
||||
export default function SettingsTab() {
|
||||
const node = useRef<HTMLDivElement>()
|
||||
const open = useSettingsMenuOpen()
|
||||
const toggle = useToggleSettingsMenu()
|
||||
|
||||
const theme = useContext(ThemeContext)
|
||||
const [userSlippageTolerance, setUserslippageTolerance] = useUserSlippageTolerance()
|
||||
|
||||
const [deadline, setDeadline] = useUserDeadline()
|
||||
|
||||
const [expertMode, toggleExpertMode] = useExpertModeManager()
|
||||
|
||||
const [darkMode, toggleDarkMode] = useDarkModeManager()
|
||||
|
||||
// show confirmation view before turning on
|
||||
const [showConfirmation, setShowConfirmation] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = e => {
|
||||
if (node.current?.contains(e.target) ?? false) {
|
||||
return
|
||||
}
|
||||
toggle()
|
||||
}
|
||||
|
||||
if (open) {
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
} else {
|
||||
document.removeEventListener('mousedown', handleClickOutside)
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside)
|
||||
}
|
||||
}, [open, toggle])
|
||||
|
||||
return (
|
||||
<StyledMenu ref={node}>
|
||||
<Modal isOpen={showConfirmation} onDismiss={() => setShowConfirmation(false)}>
|
||||
<ModalContentWrapper>
|
||||
<AutoColumn gap="lg">
|
||||
<RowBetween style={{ padding: '0 2rem' }}>
|
||||
<div />
|
||||
<Text fontWeight={500} fontSize={20}>
|
||||
Are you sure?
|
||||
</Text>
|
||||
<StyledCloseIcon onClick={() => setShowConfirmation(false)} />
|
||||
</RowBetween>
|
||||
<Break />
|
||||
<AutoColumn gap="lg" style={{ padding: '0 2rem' }}>
|
||||
<Text fontWeight={500} fontSize={20}>
|
||||
Expert mode turns off the confirm transaction prompt and allows high slippage trades that often result
|
||||
in bad rates and lost funds.
|
||||
</Text>
|
||||
<Text fontWeight={600} fontSize={20}>
|
||||
ONLY USE THIS MODE IF YOU KNOW WHAT YOU ARE DOING.
|
||||
</Text>
|
||||
<ButtonError
|
||||
error={true}
|
||||
padding={'12px'}
|
||||
onClick={() => {
|
||||
if (window.prompt(`Please type the word "confirm" to enable expert mode.`) === 'confirm') {
|
||||
toggleExpertMode()
|
||||
setShowConfirmation(false)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Text fontSize={20} fontWeight={500}>
|
||||
Turn On Expert Mode
|
||||
</Text>
|
||||
</ButtonError>
|
||||
</AutoColumn>
|
||||
</AutoColumn>
|
||||
</ModalContentWrapper>
|
||||
</Modal>
|
||||
<StyledMenuButton onClick={toggle}>
|
||||
<StyledMenuIcon />
|
||||
{expertMode && (
|
||||
<EmojiWrapper>
|
||||
<span role="img" aria-label="wizard-icon">
|
||||
🧙
|
||||
</span>
|
||||
</EmojiWrapper>
|
||||
)}
|
||||
</StyledMenuButton>
|
||||
{open && (
|
||||
<MenuFlyout>
|
||||
<AutoColumn gap="md" style={{ padding: '1rem' }}>
|
||||
<Text fontWeight={600} fontSize={14}>
|
||||
Transaction Settings
|
||||
</Text>
|
||||
<SlippageTabs
|
||||
rawSlippage={userSlippageTolerance}
|
||||
setRawSlippage={setUserslippageTolerance}
|
||||
deadline={deadline}
|
||||
setDeadline={setDeadline}
|
||||
/>
|
||||
<Text fontWeight={600} fontSize={14}>
|
||||
Interface Settings
|
||||
</Text>
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<TYPE.black fontWeight={400} fontSize={14} color={theme.text2}>
|
||||
Toggle Expert Mode
|
||||
</TYPE.black>
|
||||
<QuestionHelper text="Bypasses confirmation modals and allows high slippage trades. Use at your own risk." />
|
||||
</RowFixed>
|
||||
<Toggle
|
||||
isActive={expertMode}
|
||||
toggle={
|
||||
expertMode
|
||||
? () => {
|
||||
toggleExpertMode()
|
||||
setShowConfirmation(false)
|
||||
}
|
||||
: () => setShowConfirmation(true)
|
||||
}
|
||||
/>
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<TYPE.black fontWeight={400} fontSize={14} color={theme.text2}>
|
||||
Toggle Dark Mode
|
||||
</TYPE.black>
|
||||
</RowFixed>
|
||||
<Toggle isActive={darkMode} toggle={toggleDarkMode} />
|
||||
</RowBetween>
|
||||
</AutoColumn>
|
||||
</MenuFlyout>
|
||||
)}
|
||||
</StyledMenu>
|
||||
)
|
||||
}
|
@ -78,10 +78,6 @@ const OptionCustom = styled(FancyButton)<{ active?: boolean; warning?: boolean }
|
||||
}
|
||||
`
|
||||
|
||||
const SlippageSelector = styled.div`
|
||||
padding: 0 20px;
|
||||
`
|
||||
|
||||
export interface SlippageTabsProps {
|
||||
rawSlippage: number
|
||||
setRawSlippage: (rawSlippage: number) => void
|
||||
@ -146,15 +142,14 @@ export default function SlippageTabs({ rawSlippage, setRawSlippage, deadline, se
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<RowFixed padding={'0 20px'}>
|
||||
<TYPE.black fontWeight={400} fontSize={14} color={theme.text2}>
|
||||
Set slippage tolerance
|
||||
</TYPE.black>
|
||||
<QuestionHelper text="Your transaction will revert if the price changes unfavorably by more than this percentage." />
|
||||
</RowFixed>
|
||||
|
||||
<SlippageSelector>
|
||||
<AutoColumn gap="md">
|
||||
<AutoColumn gap="sm">
|
||||
<RowFixed>
|
||||
<TYPE.black fontWeight={400} fontSize={14} color={theme.text2}>
|
||||
Slippage tolerance
|
||||
</TYPE.black>
|
||||
<QuestionHelper text="Your transaction will revert if the price changes unfavorably by more than this percentage." />
|
||||
</RowFixed>
|
||||
<RowBetween>
|
||||
<Option
|
||||
onClick={() => {
|
||||
@ -220,16 +215,16 @@ export default function SlippageTabs({ rawSlippage, setRawSlippage, deadline, se
|
||||
: 'Your transaction may be frontrun'}
|
||||
</RowBetween>
|
||||
)}
|
||||
</SlippageSelector>
|
||||
</AutoColumn>
|
||||
|
||||
<AutoColumn gap="sm">
|
||||
<RowFixed padding={'0 20px'}>
|
||||
<RowFixed>
|
||||
<TYPE.black fontSize={14} fontWeight={400} color={theme.text2}>
|
||||
Deadline
|
||||
Transaction deadline
|
||||
</TYPE.black>
|
||||
<QuestionHelper text="Your transaction will revert if it is pending for more than this long." />
|
||||
</RowFixed>
|
||||
<RowFixed padding={'0 20px'}>
|
||||
<RowFixed>
|
||||
<OptionCustom style={{ width: '80px' }} tabIndex={-1}>
|
||||
<Input
|
||||
color={!!deadlineError ? 'red' : undefined}
|
||||
@ -246,6 +241,6 @@ export default function SlippageTabs({ rawSlippage, setRawSlippage, deadline, se
|
||||
</TYPE.body>
|
||||
</RowFixed>
|
||||
</AutoColumn>
|
||||
</>
|
||||
</AutoColumn>
|
||||
)
|
||||
}
|
||||
|
41
src/components/Toggle/index.tsx
Normal file
41
src/components/Toggle/index.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const ToggleElement = styled.span<{ isActive?: boolean; isOnSwitch?: boolean }>`
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 14px;
|
||||
background: ${({ theme, isActive, isOnSwitch }) => (isActive ? (isOnSwitch ? theme.primary1 : theme.text4) : 'none')};
|
||||
color: ${({ theme, isActive, isOnSwitch }) => (isActive ? (isOnSwitch ? theme.white : theme.text2) : theme.text3)};
|
||||
font-size: 0.825rem;
|
||||
font-weight: 400;
|
||||
`
|
||||
|
||||
const StyledToggle = styled.a<{ isActive?: boolean; activeElement?: boolean }>`
|
||||
border-radius: 16px;
|
||||
border: 1px solid ${({ theme, isActive }) => (isActive ? theme.primary5 : theme.text4)};
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
`
|
||||
|
||||
export interface ToggleProps {
|
||||
isActive: boolean
|
||||
toggle: () => void
|
||||
}
|
||||
|
||||
export default function Toggle({ isActive, toggle }: ToggleProps) {
|
||||
return (
|
||||
<StyledToggle isActive={isActive} target="_self" onClick={toggle}>
|
||||
<ToggleElement isActive={isActive} isOnSwitch={true}>
|
||||
On
|
||||
</ToggleElement>
|
||||
<ToggleElement isActive={!isActive} isOnSwitch={false}>
|
||||
Off
|
||||
</ToggleElement>
|
||||
</StyledToggle>
|
||||
)
|
||||
}
|
@ -10,10 +10,10 @@ import { AutoColumn } from '../Column'
|
||||
import { SectionBreak } from './styleds'
|
||||
import QuestionHelper from '../QuestionHelper'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
import SlippageTabs, { SlippageTabsProps } from '../SlippageTabs'
|
||||
import FormattedPriceImpact from './FormattedPriceImpact'
|
||||
import TokenLogo from '../TokenLogo'
|
||||
import flatMap from 'lodash.flatmap'
|
||||
import { useUserSlippageTolerance } from '../../state/user/hooks'
|
||||
|
||||
function TradeSummary({ trade, allowedSlippage }: { trade: Trade; allowedSlippage: number }) {
|
||||
const theme = useContext(ThemeContext)
|
||||
@ -61,20 +61,20 @@ function TradeSummary({ trade, allowedSlippage }: { trade: Trade; allowedSlippag
|
||||
</TYPE.black>
|
||||
</RowBetween>
|
||||
</AutoColumn>
|
||||
|
||||
<SectionBreak />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export interface AdvancedSwapDetailsProps extends SlippageTabsProps {
|
||||
export interface AdvancedSwapDetailsProps {
|
||||
trade?: Trade
|
||||
onDismiss: () => void
|
||||
}
|
||||
|
||||
export function AdvancedSwapDetails({ trade, onDismiss, ...slippageTabProps }: AdvancedSwapDetailsProps) {
|
||||
export function AdvancedSwapDetails({ trade, onDismiss }: AdvancedSwapDetailsProps) {
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
const [allowedSlippage] = useUserSlippageTolerance()
|
||||
|
||||
return (
|
||||
<AutoColumn gap="md">
|
||||
<CursorPointer>
|
||||
@ -85,13 +85,9 @@ export function AdvancedSwapDetails({ trade, onDismiss, ...slippageTabProps }: A
|
||||
<ChevronUp color={theme.text2} />
|
||||
</RowBetween>
|
||||
</CursorPointer>
|
||||
|
||||
<SectionBreak />
|
||||
|
||||
{trade && <TradeSummary trade={trade} allowedSlippage={slippageTabProps.rawSlippage} />}
|
||||
|
||||
<SlippageTabs {...slippageTabProps} />
|
||||
|
||||
{trade && <TradeSummary trade={trade} allowedSlippage={allowedSlippage} />}
|
||||
{trade?.route?.path?.length > 2 && <SectionBreak />}
|
||||
{trade?.route?.path?.length > 2 && (
|
||||
<AutoColumn style={{ padding: '0 20px' }}>
|
||||
<RowFixed>
|
||||
|
@ -1,30 +0,0 @@
|
||||
import { Percent } from '@uniswap/sdk'
|
||||
import React, { useContext } from 'react'
|
||||
import { Text } from 'rebass'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { YellowCard } from '../Card'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
|
||||
export function PriceSlippageWarningCard({ priceSlippage }: { priceSlippage: Percent }) {
|
||||
const theme = useContext(ThemeContext)
|
||||
return (
|
||||
<YellowCard style={{ padding: '20px', paddingTop: '10px' }}>
|
||||
<AutoColumn gap="md">
|
||||
<RowBetween>
|
||||
<RowFixed style={{ paddingTop: '8px' }}>
|
||||
<span role="img" aria-label="warning">
|
||||
⚠️
|
||||
</span>{' '}
|
||||
<Text fontWeight={500} marginLeft="4px" color={theme.text1}>
|
||||
Price Warning
|
||||
</Text>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
<Text lineHeight="145.23%;" fontSize={16} fontWeight={400} color={theme.text1}>
|
||||
This trade will move the price by ~{priceSlippage.toFixed(2)}%.
|
||||
</Text>
|
||||
</AutoColumn>
|
||||
</YellowCard>
|
||||
)
|
||||
}
|
@ -45,9 +45,15 @@ export const BottomGrouping = styled.div`
|
||||
position: relative;
|
||||
`
|
||||
|
||||
export const ErrorText = styled(Text)<{ severity?: 0 | 1 | 2 | 3 }>`
|
||||
export const ErrorText = styled(Text)<{ severity?: 0 | 1 | 2 | 3 | 4 }>`
|
||||
color: ${({ theme, severity }) =>
|
||||
severity === 3 ? theme.red1 : severity === 2 ? theme.yellow2 : severity === 1 ? theme.text1 : theme.green1};
|
||||
severity === 3 || severity === 4
|
||||
? theme.red1
|
||||
: severity === 2
|
||||
? theme.yellow2
|
||||
: severity === 1
|
||||
? theme.text1
|
||||
: theme.green1};
|
||||
`
|
||||
|
||||
export const InputGroup = styled(AutoColumn)`
|
||||
|
@ -153,6 +153,8 @@ export const BIPS_BASE = JSBI.BigInt(10000)
|
||||
export const ALLOWED_PRICE_IMPACT_LOW: Percent = new Percent(JSBI.BigInt(100), BIPS_BASE) // 1%
|
||||
export const ALLOWED_PRICE_IMPACT_MEDIUM: Percent = new Percent(JSBI.BigInt(500), BIPS_BASE) // 5%
|
||||
export const ALLOWED_PRICE_IMPACT_HIGH: Percent = new Percent(JSBI.BigInt(1000), BIPS_BASE) // 10%
|
||||
// for non expert mode disable swaps above this
|
||||
export const BLOCKED_PRICE_IMPACT_NON_EXPERT: Percent = new Percent(JSBI.BigInt(5000), BIPS_BASE) // 50%
|
||||
|
||||
// if the price slippage exceeds this number, force the user to type 'confirm' to execute
|
||||
export const PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN: Percent = new Percent(JSBI.BigInt(2500), BIPS_BASE) // 25%
|
||||
|
@ -60,8 +60,8 @@ function getSwapType(trade: Trade | undefined): SwapType | undefined {
|
||||
// and the user has approved the slippage adjusted input amount for the trade
|
||||
export function useSwapCallback(
|
||||
trade?: Trade, // trade to execute, required
|
||||
allowedSlippage: number = INITIAL_ALLOWED_SLIPPAGE, // in bips, optional
|
||||
deadline: number = DEFAULT_DEADLINE_FROM_NOW, // in seconds from now, optional
|
||||
allowedSlippage: number = INITIAL_ALLOWED_SLIPPAGE, // in bips
|
||||
deadline: number = DEFAULT_DEADLINE_FROM_NOW, // in seconds from now
|
||||
to?: string // recipient of output, optional
|
||||
): null | (() => Promise<string>) {
|
||||
const { account, chainId, library } = useActiveWeb3React()
|
||||
|
@ -17,7 +17,7 @@ import Row, { AutoRow, RowBetween, RowFixed, RowFlat } from '../../components/Ro
|
||||
|
||||
import TokenLogo from '../../components/TokenLogo'
|
||||
|
||||
import { ROUTER_ADDRESS, MIN_ETH, ONE_BIPS, DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE } from '../../constants'
|
||||
import { ROUTER_ADDRESS, MIN_ETH, ONE_BIPS } from '../../constants'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
|
||||
import { useTransactionAdder } from '../../state/transactions/hooks'
|
||||
@ -34,7 +34,7 @@ import {
|
||||
import { Field } from '../../state/mint/actions'
|
||||
import { useApproveCallback, ApprovalState } from '../../hooks/useApproveCallback'
|
||||
import { useWalletModalToggle } from '../../state/application/hooks'
|
||||
import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetailsDropdown'
|
||||
import { useUserSlippageTolerance, useUserDeadline, useIsExpertMode } from '../../state/user/hooks'
|
||||
|
||||
export default function AddLiquidity({ match: { params } }: RouteComponentProps<{ tokens: string }>) {
|
||||
useDefaultsFromURLMatchParams(params)
|
||||
@ -45,6 +45,8 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
|
||||
// toggle wallet when disconnected
|
||||
const toggleWalletModal = useWalletModalToggle()
|
||||
|
||||
const expertMode = useIsExpertMode()
|
||||
|
||||
// mint state
|
||||
const { independentField, typedValue, otherTypedValue } = useMintState()
|
||||
const {
|
||||
@ -64,14 +66,13 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
|
||||
const isValid = !error
|
||||
|
||||
// modal and loading
|
||||
const [showAdvanced, setShowAdvanced] = useState<boolean>(false) // toggling slippage, deadline, etc. on and off
|
||||
const [showConfirm, setShowConfirm] = useState<boolean>(false) // show confirmation modal
|
||||
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // waiting for user confirmaion/rejection
|
||||
const [txHash, setTxHash] = useState<string>('')
|
||||
const [showConfirm, setShowConfirm] = useState<boolean>(false)
|
||||
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirm
|
||||
|
||||
// tx parameters
|
||||
const [deadline, setDeadline] = useState<number>(DEFAULT_DEADLINE_FROM_NOW)
|
||||
const [allowedSlippage, setAllowedSlippage] = useState<number>(INITIAL_ALLOWED_SLIPPAGE)
|
||||
// txn values
|
||||
const [deadline] = useUserDeadline() // custom from users settings
|
||||
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
|
||||
const [txHash, setTxHash] = useState<string>('')
|
||||
|
||||
// get formatted amounts
|
||||
const formattedAmounts = {
|
||||
@ -427,7 +428,7 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
|
||||
)}
|
||||
<ButtonError
|
||||
onClick={() => {
|
||||
setShowConfirm(true)
|
||||
expertMode ? onAdd() : setShowConfirm(true)
|
||||
}}
|
||||
disabled={!isValid || approvalA !== ApprovalState.APPROVED || approvalB !== ApprovalState.APPROVED}
|
||||
error={!isValid && !!parsedAmounts[Field.TOKEN_A] && !!parsedAmounts[Field.TOKEN_B]}
|
||||
@ -442,17 +443,6 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
|
||||
</Wrapper>
|
||||
</AppBody>
|
||||
|
||||
{isValid && !!parsedAmounts[Field.TOKEN_A] && !!parsedAmounts[Field.TOKEN_B] ? (
|
||||
<AdvancedSwapDetailsDropdown
|
||||
rawSlippage={allowedSlippage}
|
||||
deadline={deadline}
|
||||
showAdvanced={showAdvanced}
|
||||
setShowAdvanced={setShowAdvanced}
|
||||
setDeadline={setDeadline}
|
||||
setRawSlippage={setAllowedSlippage}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{pair && !noLiquidity ? (
|
||||
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
|
||||
<PositionCard pair={pair} minimal={true} />
|
||||
|
@ -18,7 +18,7 @@ import Row, { RowBetween, RowFixed } from '../../components/Row'
|
||||
|
||||
import Slider from '../../components/Slider'
|
||||
import TokenLogo from '../../components/TokenLogo'
|
||||
import { ROUTER_ADDRESS, DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE } from '../../constants'
|
||||
import { ROUTER_ADDRESS } from '../../constants'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { usePairContract } from '../../hooks/useContract'
|
||||
|
||||
@ -31,9 +31,9 @@ import { useApproveCallback, ApprovalState } from '../../hooks/useApproveCallbac
|
||||
import { Dots } from '../../components/swap/styleds'
|
||||
import { useDefaultsFromURLMatchParams, useBurnActionHandlers } from '../../state/burn/hooks'
|
||||
import { useDerivedBurnInfo, useBurnState } from '../../state/burn/hooks'
|
||||
import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetailsDropdown'
|
||||
import { Field } from '../../state/burn/actions'
|
||||
import { useWalletModalToggle } from '../../state/application/hooks'
|
||||
import { useUserDeadline, useUserSlippageTolerance } from '../../state/user/hooks'
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
|
||||
export default function RemoveLiquidity({ match: { params } }: RouteComponentProps<{ tokens: string }>) {
|
||||
@ -52,15 +52,14 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
|
||||
const isValid = !error
|
||||
|
||||
// modal and loading
|
||||
const [showDetailed, setShowDetailed] = useState<boolean>(false) // toggling detailed view
|
||||
const [showAdvanced, setShowAdvanced] = useState<boolean>(false) // toggling slippage, deadline, etc. on and off
|
||||
const [showConfirm, setShowConfirm] = useState<boolean>(false) // show confirmation modal
|
||||
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // waiting for user confirmaion/rejection
|
||||
const [txHash, setTxHash] = useState<string>('')
|
||||
const [showConfirm, setShowConfirm] = useState<boolean>(false)
|
||||
const [showDetailed, setShowDetailed] = useState<boolean>(false)
|
||||
const [attemptingTxn, setAttemptingTxn] = useState(false) // clicked confirm
|
||||
|
||||
// tx parameters
|
||||
const [deadline, setDeadline] = useState<number>(DEFAULT_DEADLINE_FROM_NOW)
|
||||
const [allowedSlippage, setAllowedSlippage] = useState<number>(INITIAL_ALLOWED_SLIPPAGE)
|
||||
// txn values
|
||||
const [txHash, setTxHash] = useState<string>('')
|
||||
const [deadline] = useUserDeadline()
|
||||
const [allowedSlippage] = useUserSlippageTolerance()
|
||||
|
||||
const formattedAmounts = {
|
||||
[Field.LIQUIDITY_PERCENT]: parsedAmounts[Field.LIQUIDITY_PERCENT].equalTo('0')
|
||||
@ -592,17 +591,6 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
|
||||
</Wrapper>
|
||||
</AppBody>
|
||||
|
||||
{isValid ? (
|
||||
<AdvancedSwapDetailsDropdown
|
||||
rawSlippage={allowedSlippage}
|
||||
deadline={deadline}
|
||||
showAdvanced={showAdvanced}
|
||||
setShowAdvanced={setShowAdvanced}
|
||||
setDeadline={setDeadline}
|
||||
setRawSlippage={setAllowedSlippage}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{pair ? (
|
||||
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
|
||||
<PositionCard pair={pair} minimal={true} />
|
||||
|
@ -22,19 +22,14 @@ import { TransferModalHeader } from '../../components/swap/TransferModalHeader'
|
||||
import BetterTradeLink from '../../components/swap/BetterTradeLink'
|
||||
import TokenLogo from '../../components/TokenLogo'
|
||||
import { TokenWarningCards } from '../../components/TokenWarningCard'
|
||||
import {
|
||||
DEFAULT_DEADLINE_FROM_NOW,
|
||||
INITIAL_ALLOWED_SLIPPAGE,
|
||||
MIN_ETH,
|
||||
BETTER_TRADE_LINK_THRESHOLD
|
||||
} from '../../constants'
|
||||
import { INITIAL_ALLOWED_SLIPPAGE, MIN_ETH, BETTER_TRADE_LINK_THRESHOLD } from '../../constants'
|
||||
import { getTradeVersion, isTradeBetter } from '../../data/V1'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useApproveCallbackFromTrade, ApprovalState } from '../../hooks/useApproveCallback'
|
||||
import { useSendCallback } from '../../hooks/useSendCallback'
|
||||
import { useSwapCallback } from '../../hooks/useSwapCallback'
|
||||
import { useWalletModalToggle, useToggleSettingsMenu } from '../../state/application/hooks'
|
||||
import useToggledVersion, { Version } from '../../hooks/useToggledVersion'
|
||||
import { useWalletModalToggle } from '../../state/application/hooks'
|
||||
import { Field } from '../../state/swap/actions'
|
||||
import {
|
||||
useDefaultsFromURLSearch,
|
||||
@ -46,7 +41,8 @@ import { useAllTokenBalancesTreatingWETHasETH } from '../../state/wallet/hooks'
|
||||
import { CursorPointer, TYPE } from '../../theme'
|
||||
import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown, warningSeverity } from '../../utils/prices'
|
||||
import AppBody from '../AppBody'
|
||||
import { PriceSlippageWarningCard } from '../../components/swap/PriceSlippageWarningCard'
|
||||
import { useUserSlippageTolerance, useUserDeadline, useExpertModeManager } from '../../state/user/hooks'
|
||||
import { ClickableText } from '../Pool/styleds'
|
||||
|
||||
export default function Send() {
|
||||
useDefaultsFromURLSearch()
|
||||
@ -59,6 +55,10 @@ export default function Send() {
|
||||
// toggle wallet when disconnected
|
||||
const toggleWalletModal = useWalletModalToggle()
|
||||
|
||||
// for expert mode
|
||||
const toggleSettings = useToggleSettingsMenu()
|
||||
const [expertMode] = useExpertModeManager()
|
||||
|
||||
// sending state
|
||||
const [sendingWithSwap, setSendingWithSwap] = useState<boolean>(false)
|
||||
const [recipient, setRecipient] = useState<string>('')
|
||||
@ -102,10 +102,8 @@ export default function Send() {
|
||||
const [showConfirm, setShowConfirm] = useState<boolean>(false) // show confirmation modal
|
||||
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // waiting for user confirmaion/rejection
|
||||
const [txHash, setTxHash] = useState<string>('')
|
||||
|
||||
// tx parameters
|
||||
const [deadline, setDeadline] = useState<number>(DEFAULT_DEADLINE_FROM_NOW)
|
||||
const [allowedSlippage, setAllowedSlippage] = useState<number>(INITIAL_ALLOWED_SLIPPAGE)
|
||||
const [deadline] = useUserDeadline() // custom from user settings
|
||||
const [allowedSlippage] = useUserSlippageTolerance() // custom from user settings
|
||||
|
||||
const route = bestTrade?.route
|
||||
const userHasSpecifiedInputOutput =
|
||||
@ -223,7 +221,8 @@ export default function Send() {
|
||||
((sendingWithSwap && isSwapValid) || (!sendingWithSwap && isSendValid)) &&
|
||||
(approval === ApprovalState.NOT_APPROVED ||
|
||||
approval === ApprovalState.PENDING ||
|
||||
(approvalSubmitted && approval === ApprovalState.APPROVED))
|
||||
(approvalSubmitted && approval === ApprovalState.APPROVED)) &&
|
||||
!(severity > 3 && !expertMode)
|
||||
|
||||
function modalHeader() {
|
||||
if (!sendingWithSwap) {
|
||||
@ -487,6 +486,20 @@ export default function Send() {
|
||||
<TradePrice showInverted={showInverted} setShowInverted={setShowInverted} trade={bestTrade} />
|
||||
</RowBetween>
|
||||
|
||||
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
|
||||
<RowBetween align="center">
|
||||
<ClickableText>
|
||||
<Text fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
|
||||
Slippage Tolerance
|
||||
</Text>
|
||||
</ClickableText>
|
||||
<ClickableText>
|
||||
<Text fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
|
||||
{allowedSlippage ? allowedSlippage / 100 : '-'}%
|
||||
</Text>
|
||||
</ClickableText>
|
||||
</RowBetween>
|
||||
)}
|
||||
{bestTrade && severity > 1 && (
|
||||
<RowBetween>
|
||||
<TYPE.main
|
||||
@ -536,7 +549,7 @@ export default function Send() {
|
||||
</ButtonPrimary>
|
||||
<ButtonError
|
||||
onClick={() => {
|
||||
setShowConfirm(true)
|
||||
expertMode ? (sendingWithSwap ? onSwap() : onSend()) : setShowConfirm(true)
|
||||
}}
|
||||
width="48%"
|
||||
id="send-button"
|
||||
@ -544,23 +557,28 @@ export default function Send() {
|
||||
error={sendingWithSwap && isSwapValid && severity > 2}
|
||||
>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
{`Send${severity > 2 ? ' Anyway' : ''}`}
|
||||
{severity > 3 && !expertMode ? `Price Impact High` : `Send${severity > 2 ? ' Anyway' : ''}`}
|
||||
</Text>
|
||||
</ButtonError>
|
||||
</RowBetween>
|
||||
) : (
|
||||
<ButtonError
|
||||
onClick={() => {
|
||||
setShowConfirm(true)
|
||||
expertMode ? (sendingWithSwap ? onSwap() : onSend()) : setShowConfirm(true)
|
||||
}}
|
||||
id="send-button"
|
||||
disabled={(sendingWithSwap && !isSwapValid) || (!sendingWithSwap && !isSendValid)}
|
||||
disabled={
|
||||
(sendingWithSwap && !isSwapValid) ||
|
||||
(!sendingWithSwap && !isSendValid) ||
|
||||
(severity > 3 && !expertMode && sendingWithSwap)
|
||||
}
|
||||
error={sendingWithSwap && isSwapValid && severity > 2}
|
||||
>
|
||||
<Text fontSize={20} fontWeight={500}>
|
||||
{(sendingWithSwap ? swapError : null) ||
|
||||
sendAmountError ||
|
||||
recipientError ||
|
||||
(severity > 3 && !expertMode && `Price Impact Too High`) ||
|
||||
`Send${severity > 2 ? ' Anyway' : ''}`}
|
||||
</Text>
|
||||
</ButtonError>
|
||||
@ -571,21 +589,7 @@ export default function Send() {
|
||||
</AppBody>
|
||||
|
||||
{bestTrade && (
|
||||
<AdvancedSwapDetailsDropdown
|
||||
trade={bestTrade}
|
||||
rawSlippage={allowedSlippage}
|
||||
deadline={deadline}
|
||||
showAdvanced={showAdvanced}
|
||||
setShowAdvanced={setShowAdvanced}
|
||||
setDeadline={setDeadline}
|
||||
setRawSlippage={setAllowedSlippage}
|
||||
/>
|
||||
)}
|
||||
|
||||
{priceImpactWithoutFee && severity > 2 && (
|
||||
<AutoColumn gap="lg" style={{ marginTop: '1rem' }}>
|
||||
<PriceSlippageWarningCard priceSlippage={priceImpactWithoutFee} />
|
||||
</AutoColumn>
|
||||
<AdvancedSwapDetailsDropdown trade={bestTrade} showAdvanced={showAdvanced} setShowAdvanced={setShowAdvanced} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
@ -20,18 +20,15 @@ import SwapModalHeader from '../../components/swap/SwapModalHeader'
|
||||
import TradePrice from '../../components/swap/TradePrice'
|
||||
import BetterTradeLink from '../../components/swap/BetterTradeLink'
|
||||
import { TokenWarningCards } from '../../components/TokenWarningCard'
|
||||
import {
|
||||
DEFAULT_DEADLINE_FROM_NOW,
|
||||
INITIAL_ALLOWED_SLIPPAGE,
|
||||
MIN_ETH,
|
||||
BETTER_TRADE_LINK_THRESHOLD
|
||||
} from '../../constants'
|
||||
import { getTradeVersion, isTradeBetter } from '../../data/V1'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useApproveCallbackFromTrade, ApprovalState } from '../../hooks/useApproveCallback'
|
||||
import { useSwapCallback } from '../../hooks/useSwapCallback'
|
||||
import { useWalletModalToggle, useToggleSettingsMenu } from '../../state/application/hooks'
|
||||
import { useExpertModeManager, useUserSlippageTolerance, useUserDeadline } from '../../state/user/hooks'
|
||||
|
||||
import { INITIAL_ALLOWED_SLIPPAGE, MIN_ETH, BETTER_TRADE_LINK_THRESHOLD } from '../../constants'
|
||||
import { getTradeVersion, isTradeBetter } from '../../data/V1'
|
||||
import useToggledVersion, { Version } from '../../hooks/useToggledVersion'
|
||||
import { useWalletModalToggle } from '../../state/application/hooks'
|
||||
import { Field } from '../../state/swap/actions'
|
||||
import {
|
||||
useDefaultsFromURLSearch,
|
||||
@ -42,7 +39,7 @@ import {
|
||||
import { CursorPointer, TYPE } from '../../theme'
|
||||
import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown, warningSeverity } from '../../utils/prices'
|
||||
import AppBody from '../AppBody'
|
||||
import { PriceSlippageWarningCard } from '../../components/swap/PriceSlippageWarningCard'
|
||||
import { ClickableText } from '../Pool/styleds'
|
||||
|
||||
export default function Swap() {
|
||||
useDefaultsFromURLSearch()
|
||||
@ -53,6 +50,14 @@ export default function Swap() {
|
||||
// toggle wallet when disconnected
|
||||
const toggleWalletModal = useWalletModalToggle()
|
||||
|
||||
// for expert mode
|
||||
const toggleSettings = useToggleSettingsMenu()
|
||||
const [expertMode] = useExpertModeManager()
|
||||
|
||||
// get custom setting values for user
|
||||
const [deadline] = useUserDeadline()
|
||||
const [allowedSlippage] = useUserSlippageTolerance()
|
||||
|
||||
// swap state
|
||||
const { independentField, typedValue } = useSwapState()
|
||||
const { bestTrade: bestTradeV2, tokenBalances, parsedAmount, tokens, error, v1Trade } = useDerivedSwapInfo()
|
||||
@ -84,10 +89,6 @@ export default function Swap() {
|
||||
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // waiting for user confirmaion/rejection
|
||||
const [txHash, setTxHash] = useState<string>('')
|
||||
|
||||
// tx parameters
|
||||
const [deadline, setDeadline] = useState<number>(DEFAULT_DEADLINE_FROM_NOW)
|
||||
const [allowedSlippage, setAllowedSlippage] = useState<number>(INITIAL_ALLOWED_SLIPPAGE)
|
||||
|
||||
const formattedAmounts = {
|
||||
[independentField]: typedValue,
|
||||
[dependentField]: parsedAmounts[dependentField] ? parsedAmounts[dependentField].toSignificant(6) : ''
|
||||
@ -107,13 +108,6 @@ export default function Swap() {
|
||||
// check if user has gone through approval process, used to show two step buttons, reset on token change
|
||||
const [approvalSubmitted, setApprovalSubmitted] = useState<boolean>(false)
|
||||
|
||||
// show approve flow when: no error on inputs, not approved or pending, or approved in current session
|
||||
const showApproveFlow =
|
||||
!error &&
|
||||
(approval === ApprovalState.NOT_APPROVED ||
|
||||
approval === ApprovalState.PENDING ||
|
||||
(approvalSubmitted && approval === ApprovalState.APPROVED))
|
||||
|
||||
// mark when a user has submitted an approval, reset onTokenSelection for input field
|
||||
useEffect(() => {
|
||||
if (approval === ApprovalState.PENDING) {
|
||||
@ -146,7 +140,6 @@ export default function Swap() {
|
||||
if (priceImpactWithoutFee && !confirmPriceImpactWithoutFee(priceImpactWithoutFee)) {
|
||||
return
|
||||
}
|
||||
|
||||
setAttemptingTxn(true)
|
||||
swapCallback()
|
||||
.then(hash => {
|
||||
@ -178,6 +171,15 @@ export default function Swap() {
|
||||
// warnings on slippage
|
||||
const priceImpactSeverity = warningSeverity(priceImpactWithoutFee)
|
||||
|
||||
// show approve flow when: no error on inputs, not approved or pending, or approved in current session
|
||||
// never show if price impact is above threshold in non expert mode
|
||||
const showApproveFlow =
|
||||
!error &&
|
||||
(approval === ApprovalState.NOT_APPROVED ||
|
||||
approval === ApprovalState.PENDING ||
|
||||
(approvalSubmitted && approval === ApprovalState.APPROVED)) &&
|
||||
!(priceImpactSeverity > 3 && !expertMode)
|
||||
|
||||
function modalHeader() {
|
||||
return (
|
||||
<SwapModalHeader
|
||||
@ -292,6 +294,17 @@ export default function Swap() {
|
||||
<TradePrice trade={bestTrade} showInverted={showInverted} setShowInverted={setShowInverted} />
|
||||
</RowBetween>
|
||||
|
||||
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
|
||||
<RowBetween align="center">
|
||||
<ClickableText fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
|
||||
Slippage Tolerance
|
||||
</ClickableText>
|
||||
<ClickableText fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
|
||||
{allowedSlippage ? allowedSlippage / 100 : '-'}%
|
||||
</ClickableText>
|
||||
</RowBetween>
|
||||
)}
|
||||
|
||||
{bestTrade && priceImpactSeverity > 1 && (
|
||||
<RowBetween>
|
||||
<TYPE.main
|
||||
@ -335,29 +348,35 @@ export default function Swap() {
|
||||
</ButtonPrimary>
|
||||
<ButtonError
|
||||
onClick={() => {
|
||||
setShowConfirm(true)
|
||||
expertMode ? onSwap() : setShowConfirm(true)
|
||||
}}
|
||||
width="48%"
|
||||
id="swap-button"
|
||||
disabled={!isValid || approval !== ApprovalState.APPROVED}
|
||||
disabled={!isValid || approval !== ApprovalState.APPROVED || (priceImpactSeverity > 3 && !expertMode)}
|
||||
error={isValid && priceImpactSeverity > 2}
|
||||
>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
{`Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
|
||||
{priceImpactSeverity > 3 && !expertMode
|
||||
? `Price Impact High`
|
||||
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
|
||||
</Text>
|
||||
</ButtonError>
|
||||
</RowBetween>
|
||||
) : (
|
||||
<ButtonError
|
||||
onClick={() => {
|
||||
setShowConfirm(true)
|
||||
expertMode ? onSwap() : setShowConfirm(true)
|
||||
}}
|
||||
id="swap-button"
|
||||
disabled={!isValid}
|
||||
disabled={!isValid || (priceImpactSeverity > 3 && !expertMode)}
|
||||
error={isValid && priceImpactSeverity > 2}
|
||||
>
|
||||
<Text fontSize={20} fontWeight={500}>
|
||||
{error ?? `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
|
||||
{error
|
||||
? error
|
||||
: priceImpactSeverity > 3 && !expertMode
|
||||
? `Price Impact Too High`
|
||||
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
|
||||
</Text>
|
||||
</ButtonError>
|
||||
)}
|
||||
@ -367,21 +386,7 @@ export default function Swap() {
|
||||
</AppBody>
|
||||
|
||||
{bestTrade && (
|
||||
<AdvancedSwapDetailsDropdown
|
||||
trade={bestTrade}
|
||||
rawSlippage={allowedSlippage}
|
||||
deadline={deadline}
|
||||
showAdvanced={showAdvanced}
|
||||
setShowAdvanced={setShowAdvanced}
|
||||
setDeadline={setDeadline}
|
||||
setRawSlippage={setAllowedSlippage}
|
||||
/>
|
||||
)}
|
||||
|
||||
{priceImpactWithoutFee && priceImpactSeverity > 2 && (
|
||||
<AutoColumn gap="lg" style={{ marginTop: '1rem' }}>
|
||||
<PriceSlippageWarningCard priceSlippage={priceImpactWithoutFee} />
|
||||
</AutoColumn>
|
||||
<AdvancedSwapDetailsDropdown trade={bestTrade} showAdvanced={showAdvanced} setShowAdvanced={setShowAdvanced} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
@ -23,5 +23,6 @@ export type PopupContent =
|
||||
|
||||
export const updateBlockNumber = createAction<{ chainId: number; blockNumber: number }>('updateBlockNumber')
|
||||
export const toggleWalletModal = createAction<void>('toggleWalletModal')
|
||||
export const toggleSettingsMenu = createAction<void>('toggleSettingsMenu')
|
||||
export const addPopup = createAction<{ key?: string; content: PopupContent }>('addPopup')
|
||||
export const removePopup = createAction<{ key: string }>('removePopup')
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { addPopup, PopupContent, removePopup, toggleWalletModal } from './actions'
|
||||
import { addPopup, PopupContent, removePopup, toggleWalletModal, toggleSettingsMenu } from './actions'
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import { AppState } from '../index'
|
||||
|
||||
@ -19,6 +19,15 @@ export function useWalletModalToggle(): () => void {
|
||||
return useCallback(() => dispatch(toggleWalletModal()), [dispatch])
|
||||
}
|
||||
|
||||
export function useSettingsMenuOpen(): boolean {
|
||||
return useSelector((state: AppState) => state.application.settingsMenuOpen)
|
||||
}
|
||||
|
||||
export function useToggleSettingsMenu(): () => void {
|
||||
const dispatch = useDispatch()
|
||||
return useCallback(() => dispatch(toggleSettingsMenu()), [dispatch])
|
||||
}
|
||||
|
||||
// returns a function that allows adding a popup
|
||||
export function useAddPopup(): (content: PopupContent, key?: string) => void {
|
||||
const dispatch = useDispatch()
|
||||
|
@ -1,5 +1,12 @@
|
||||
import { createReducer, nanoid } from '@reduxjs/toolkit'
|
||||
import { addPopup, PopupContent, removePopup, toggleWalletModal, updateBlockNumber } from './actions'
|
||||
import {
|
||||
addPopup,
|
||||
PopupContent,
|
||||
removePopup,
|
||||
toggleWalletModal,
|
||||
toggleSettingsMenu,
|
||||
updateBlockNumber
|
||||
} from './actions'
|
||||
|
||||
type PopupList = Array<{ key: string; show: boolean; content: PopupContent }>
|
||||
|
||||
@ -7,12 +14,14 @@ interface ApplicationState {
|
||||
blockNumber: { [chainId: number]: number }
|
||||
popupList: PopupList
|
||||
walletModalOpen: boolean
|
||||
settingsMenuOpen: boolean
|
||||
}
|
||||
|
||||
const initialState: ApplicationState = {
|
||||
blockNumber: {},
|
||||
popupList: [],
|
||||
walletModalOpen: false
|
||||
walletModalOpen: false,
|
||||
settingsMenuOpen: false
|
||||
}
|
||||
|
||||
export default createReducer(initialState, builder =>
|
||||
@ -28,6 +37,9 @@ export default createReducer(initialState, builder =>
|
||||
.addCase(toggleWalletModal, state => {
|
||||
state.walletModalOpen = !state.walletModalOpen
|
||||
})
|
||||
.addCase(toggleSettingsMenu, state => {
|
||||
state.settingsMenuOpen = !state.settingsMenuOpen
|
||||
})
|
||||
.addCase(addPopup, (state, { payload: { content, key } }) => {
|
||||
if (key && state.popupList.some(popup => popup.key === key)) return
|
||||
state.popupList.push({
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { Version } from './../../hooks/useToggledVersion'
|
||||
import { parseUnits } from '@ethersproject/units'
|
||||
import { ChainId, JSBI, Token, TokenAmount, Trade, WETH } from '@uniswap/sdk'
|
||||
import { ParsedQs } from 'qs'
|
||||
@ -13,6 +14,9 @@ import { AppDispatch, AppState } from '../index'
|
||||
import { useTokenBalancesTreatWETHAsETH } from '../wallet/hooks'
|
||||
import { Field, replaceSwapState, selectToken, switchTokens, typeInput } from './actions'
|
||||
import { SwapState } from './reducer'
|
||||
import useToggledVersion from '../../hooks/useToggledVersion'
|
||||
import { useUserSlippageTolerance } from '../user/hooks'
|
||||
import { computeSlippageAdjustedAmounts } from '../../utils/prices'
|
||||
|
||||
export function useSwapState(): AppState['swap'] {
|
||||
return useSelector<AppState, AppState['swap']>(state => state.swap)
|
||||
@ -83,6 +87,8 @@ export function useDerivedSwapInfo(): {
|
||||
} {
|
||||
const { account } = useActiveWeb3React()
|
||||
|
||||
const toggledVersion = useToggledVersion()
|
||||
|
||||
const {
|
||||
independentField,
|
||||
typedValue,
|
||||
@ -132,12 +138,29 @@ export function useDerivedSwapInfo(): {
|
||||
error = error ?? 'Select a token'
|
||||
}
|
||||
|
||||
// this check is incorrect, it should check against the maximum amount in
|
||||
// rather than the estimated amount in
|
||||
// const [balanceIn, amountIn] = [tokenBalances[Field.INPUT], parsedAmounts[Field.INPUT]]
|
||||
// if (balanceIn && amountIn && balanceIn.lessThan(amountIn)) {
|
||||
// error = 'Insufficient ' + amountIn.token.symbol + ' balance'
|
||||
// }
|
||||
const [allowedSlippage] = useUserSlippageTolerance()
|
||||
|
||||
const slippageAdjustedAmounts =
|
||||
bestTrade && allowedSlippage && computeSlippageAdjustedAmounts(bestTrade, allowedSlippage)
|
||||
|
||||
const slippageAdjustedAmountsV1 =
|
||||
v1Trade && allowedSlippage && computeSlippageAdjustedAmounts(v1Trade, allowedSlippage)
|
||||
|
||||
// compare input balance to MAx input based on version
|
||||
const [balanceIn, amountIn] = [
|
||||
tokenBalances[Field.INPUT],
|
||||
toggledVersion === Version.v1
|
||||
? slippageAdjustedAmountsV1
|
||||
? slippageAdjustedAmountsV1[Field.INPUT]
|
||||
: null
|
||||
: slippageAdjustedAmounts
|
||||
? slippageAdjustedAmounts[Field.INPUT]
|
||||
: null
|
||||
]
|
||||
|
||||
if (balanceIn && amountIn && balanceIn.lessThan(amountIn)) {
|
||||
error = 'Insufficient ' + amountIn.token.symbol + ' balance'
|
||||
}
|
||||
|
||||
return {
|
||||
tokens,
|
||||
|
@ -16,6 +16,11 @@ export interface SerializedPair {
|
||||
export const updateVersion = createAction<void>('updateVersion')
|
||||
export const updateMatchesDarkMode = createAction<{ matchesDarkMode: boolean }>('updateMatchesDarkMode')
|
||||
export const updateUserDarkMode = createAction<{ userDarkMode: boolean }>('updateUserDarkMode')
|
||||
export const updateUserExpertMode = createAction<{ userExpertMode: boolean }>('updateUserExpertMode')
|
||||
export const updateUserSlippageTolerance = createAction<{ userSlippageTolerance: number }>(
|
||||
'updateUserSlippageTolerance'
|
||||
)
|
||||
export const updateUserDeadline = createAction<{ userDeadline: number }>('updateUserDeadline')
|
||||
export const addSerializedToken = createAction<{ serializedToken: SerializedToken }>('addSerializedToken')
|
||||
export const removeSerializedToken = createAction<{ chainId: number; address: string }>('removeSerializedToken')
|
||||
export const addSerializedPair = createAction<{ serializedPair: SerializedPair }>('addSerializedPair')
|
||||
|
@ -13,7 +13,10 @@ import {
|
||||
removeSerializedToken,
|
||||
SerializedPair,
|
||||
SerializedToken,
|
||||
updateUserDarkMode
|
||||
updateUserDarkMode,
|
||||
updateUserExpertMode,
|
||||
updateUserSlippageTolerance,
|
||||
updateUserDeadline
|
||||
} from './actions'
|
||||
import { BASES_TO_TRACK_LIQUIDITY_FOR, DUMMY_PAIRS_TO_PIN } from '../../constants'
|
||||
|
||||
@ -63,6 +66,54 @@ export function useDarkModeManager(): [boolean, () => void] {
|
||||
return [darkMode, toggleSetDarkMode]
|
||||
}
|
||||
|
||||
export function useIsExpertMode(): boolean {
|
||||
const userExpertMode = useSelector<AppState, AppState['user']['userExpertMode']>(state => state.user.userExpertMode)
|
||||
return userExpertMode
|
||||
}
|
||||
|
||||
export function useExpertModeManager(): [boolean, () => void] {
|
||||
const dispatch = useDispatch<AppDispatch>()
|
||||
const expertMode = useIsExpertMode()
|
||||
|
||||
const toggleSetExpertMode = useCallback(() => {
|
||||
dispatch(updateUserExpertMode({ userExpertMode: !expertMode }))
|
||||
}, [expertMode, dispatch])
|
||||
|
||||
return [expertMode, toggleSetExpertMode]
|
||||
}
|
||||
|
||||
export function useUserSlippageTolerance(): [number, (slippage: number) => void] {
|
||||
const dispatch = useDispatch<AppDispatch>()
|
||||
const userSlippageTolerance = useSelector<AppState, AppState['user']['userSlippageTolerance']>(state => {
|
||||
return state.user.userSlippageTolerance
|
||||
})
|
||||
|
||||
const setUserSlippageTolerance = useCallback(
|
||||
(userSlippageTolerance: number) => {
|
||||
dispatch(updateUserSlippageTolerance({ userSlippageTolerance }))
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
return [userSlippageTolerance, setUserSlippageTolerance]
|
||||
}
|
||||
|
||||
export function useUserDeadline(): [number, (slippage: number) => void] {
|
||||
const dispatch = useDispatch<AppDispatch>()
|
||||
const userDeadline = useSelector<AppState, AppState['user']['userDeadline']>(state => {
|
||||
return state.user.userDeadline
|
||||
})
|
||||
|
||||
const setUserDeadline = useCallback(
|
||||
(userDeadline: number) => {
|
||||
dispatch(updateUserDeadline({ userDeadline }))
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
return [userDeadline, setUserDeadline]
|
||||
}
|
||||
|
||||
export function useAddUserToken(): (token: Token) => void {
|
||||
const dispatch = useDispatch<AppDispatch>()
|
||||
return useCallback(
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { INITIAL_ALLOWED_SLIPPAGE, DEFAULT_DEADLINE_FROM_NOW } from './../../constants/index'
|
||||
import { createReducer } from '@reduxjs/toolkit'
|
||||
import { ChainId, WETH } from '@uniswap/sdk'
|
||||
import {
|
||||
addSerializedPair,
|
||||
addSerializedToken,
|
||||
@ -10,7 +10,10 @@ import {
|
||||
SerializedToken,
|
||||
updateMatchesDarkMode,
|
||||
updateUserDarkMode,
|
||||
updateVersion
|
||||
updateVersion,
|
||||
updateUserExpertMode,
|
||||
updateUserSlippageTolerance,
|
||||
updateUserDeadline
|
||||
} from './actions'
|
||||
|
||||
const currentTimestamp = () => new Date().getTime()
|
||||
@ -21,6 +24,14 @@ interface UserState {
|
||||
userDarkMode: boolean | null // the user's choice for dark mode or light mode
|
||||
matchesDarkMode: boolean // whether the dark mode media query matches
|
||||
|
||||
userExpertMode: boolean
|
||||
|
||||
// user defined slippage tolerance in bips, used in all txns
|
||||
userSlippageTolerance: number
|
||||
|
||||
// deadline set by user in minutes, used in all txns
|
||||
userDeadline: number
|
||||
|
||||
tokens: {
|
||||
[chainId: number]: {
|
||||
[address: string]: SerializedToken
|
||||
@ -50,13 +61,13 @@ function pairKey(token0Address: string, token1Address: string) {
|
||||
|
||||
const initialState: UserState = {
|
||||
lastVersion: '',
|
||||
|
||||
userDarkMode: null,
|
||||
matchesDarkMode: false,
|
||||
|
||||
userExpertMode: false,
|
||||
userSlippageTolerance: INITIAL_ALLOWED_SLIPPAGE,
|
||||
userDeadline: DEFAULT_DEADLINE_FROM_NOW,
|
||||
tokens: {},
|
||||
pairs: {},
|
||||
|
||||
timestamp: currentTimestamp()
|
||||
}
|
||||
|
||||
@ -68,12 +79,14 @@ export default createReducer(initialState, builder =>
|
||||
if (GIT_COMMIT_HASH && state.lastVersion !== GIT_COMMIT_HASH) {
|
||||
state.lastVersion = GIT_COMMIT_HASH
|
||||
|
||||
// Wed May 20, 2020 @ ~9pm central
|
||||
if (state.timestamp < 1590027589111) {
|
||||
// this should remove the user added token from 'eth' for mainnet
|
||||
if (state.tokens[ChainId.MAINNET]) {
|
||||
delete state.tokens[ChainId.MAINNET][WETH[ChainId.MAINNET].address]
|
||||
}
|
||||
// slippage isnt being tracked in local storage, reset to default
|
||||
if (typeof state.userSlippageTolerance !== 'number') {
|
||||
state.userSlippageTolerance = INITIAL_ALLOWED_SLIPPAGE
|
||||
}
|
||||
|
||||
// deadline isnt being tracked in local storage, reset to default
|
||||
if (typeof state.userDeadline !== 'number') {
|
||||
state.userDeadline = DEFAULT_DEADLINE_FROM_NOW
|
||||
}
|
||||
}
|
||||
state.timestamp = currentTimestamp()
|
||||
@ -86,6 +99,18 @@ export default createReducer(initialState, builder =>
|
||||
state.matchesDarkMode = action.payload.matchesDarkMode
|
||||
state.timestamp = currentTimestamp()
|
||||
})
|
||||
.addCase(updateUserExpertMode, (state, action) => {
|
||||
state.userExpertMode = action.payload.userExpertMode
|
||||
state.timestamp = currentTimestamp()
|
||||
})
|
||||
.addCase(updateUserSlippageTolerance, (state, action) => {
|
||||
state.userSlippageTolerance = action.payload.userSlippageTolerance
|
||||
state.timestamp = currentTimestamp()
|
||||
})
|
||||
.addCase(updateUserDeadline, (state, action) => {
|
||||
state.userDeadline = action.payload.userDeadline
|
||||
state.timestamp = currentTimestamp()
|
||||
})
|
||||
.addCase(addSerializedToken, (state, { payload: { serializedToken } }) => {
|
||||
state.tokens[serializedToken.chainId] = state.tokens[serializedToken.chainId] || {}
|
||||
state.tokens[serializedToken.chainId][serializedToken.address] = serializedToken
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { BLOCKED_PRICE_IMPACT_NON_EXPERT } from './../constants/index'
|
||||
import { Fraction, JSBI, Percent, TokenAmount, Trade } from '@uniswap/sdk'
|
||||
import { ALLOWED_PRICE_IMPACT_HIGH, ALLOWED_PRICE_IMPACT_LOW, ALLOWED_PRICE_IMPACT_MEDIUM } from '../constants'
|
||||
import { Field } from '../state/swap/actions'
|
||||
@ -51,7 +52,8 @@ export function computeSlippageAdjustedAmounts(
|
||||
}
|
||||
}
|
||||
|
||||
export function warningSeverity(priceImpact: Percent): 0 | 1 | 2 | 3 {
|
||||
export function warningSeverity(priceImpact: Percent): 0 | 1 | 2 | 3 | 4 {
|
||||
if (!priceImpact?.lessThan(BLOCKED_PRICE_IMPACT_NON_EXPERT)) return 4
|
||||
if (!priceImpact?.lessThan(ALLOWED_PRICE_IMPACT_HIGH)) return 3
|
||||
if (!priceImpact?.lessThan(ALLOWED_PRICE_IMPACT_MEDIUM)) return 2
|
||||
if (!priceImpact?.lessThan(ALLOWED_PRICE_IMPACT_LOW)) return 1
|
||||
|
Loading…
Reference in New Issue
Block a user