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 {
|
&:disabled {
|
||||||
opacity: 50%;
|
opacity: 50%;
|
||||||
cursor: auto;
|
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 { ExternalLink, StyledInternalLink } from '../../theme'
|
||||||
import { YellowCard } from '../Card'
|
import { YellowCard } from '../Card'
|
||||||
import { AutoColumn } from '../Column'
|
import { AutoColumn } from '../Column'
|
||||||
|
import Settings from '../Settings'
|
||||||
import Menu from '../Menu'
|
import Menu from '../Menu'
|
||||||
|
|
||||||
import Row, { RowBetween } from '../Row'
|
import Row, { RowBetween } from '../Row'
|
||||||
@ -31,15 +32,12 @@ const HeaderFrame = styled.div`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
top: 0;
|
top: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
${({ theme }) => theme.mediaWidth.upToExtraSmall`
|
${({ theme }) => theme.mediaWidth.upToExtraSmall`
|
||||||
padding: 12px 0 0 0;
|
padding: 12px 0 0 0;
|
||||||
width: calc(100%);
|
width: calc(100%);
|
||||||
position: relative;
|
position: relative;
|
||||||
`};
|
`};
|
||||||
z-index: 2;
|
|
||||||
`
|
`
|
||||||
|
|
||||||
const HeaderElement = styled.div`
|
const HeaderElement = styled.div`
|
||||||
@ -130,6 +128,12 @@ const NETWORK_LABELS: { [chainId in ChainId]: string | null } = {
|
|||||||
[ChainId.KOVAN]: 'Kovan'
|
[ChainId.KOVAN]: 'Kovan'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const BalanceWrapper = styled.div`
|
||||||
|
${({ theme }) => theme.mediaWidth.upToExtraSmall`
|
||||||
|
display: none;
|
||||||
|
`};
|
||||||
|
`
|
||||||
|
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
const { account, chainId } = useActiveWeb3React()
|
const { account, chainId } = useActiveWeb3React()
|
||||||
|
|
||||||
@ -174,16 +178,17 @@ export default function Header() {
|
|||||||
{!isMobile && NETWORK_LABELS[chainId] && <NetworkCard>{NETWORK_LABELS[chainId]}</NetworkCard>}
|
{!isMobile && NETWORK_LABELS[chainId] && <NetworkCard>{NETWORK_LABELS[chainId]}</NetworkCard>}
|
||||||
</TestnetWrapper>
|
</TestnetWrapper>
|
||||||
<AccountElement active={!!account} style={{ pointerEvents: 'auto' }}>
|
<AccountElement active={!!account} style={{ pointerEvents: 'auto' }}>
|
||||||
|
<BalanceWrapper>
|
||||||
{account && userEthBalance ? (
|
{account && userEthBalance ? (
|
||||||
<Text style={{ flexShrink: 0 }} pl="0.75rem" pr="0.5rem" fontWeight={500}>
|
<Text style={{ flexShrink: 0 }} pl="0.75rem" pr="0.5rem" fontWeight={500}>
|
||||||
{userEthBalance?.toSignificant(4)} ETH
|
{userEthBalance?.toSignificant(4)} ETH
|
||||||
</Text>
|
</Text>
|
||||||
) : null}
|
) : null}
|
||||||
|
</BalanceWrapper>
|
||||||
<Web3Status />
|
<Web3Status />
|
||||||
</AccountElement>
|
</AccountElement>
|
||||||
<div style={{ pointerEvents: 'auto' }}>
|
<Settings />
|
||||||
<Menu />
|
<Menu />
|
||||||
</div>
|
|
||||||
</HeaderElement>
|
</HeaderElement>
|
||||||
</RowBetween>
|
</RowBetween>
|
||||||
</HeaderFrame>
|
</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 {
|
export interface SlippageTabsProps {
|
||||||
rawSlippage: number
|
rawSlippage: number
|
||||||
setRawSlippage: (rawSlippage: number) => void
|
setRawSlippage: (rawSlippage: number) => void
|
||||||
@ -146,15 +142,14 @@ export default function SlippageTabs({ rawSlippage, setRawSlippage, deadline, se
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<AutoColumn gap="md">
|
||||||
<RowFixed padding={'0 20px'}>
|
<AutoColumn gap="sm">
|
||||||
|
<RowFixed>
|
||||||
<TYPE.black fontWeight={400} fontSize={14} color={theme.text2}>
|
<TYPE.black fontWeight={400} fontSize={14} color={theme.text2}>
|
||||||
Set slippage tolerance
|
Slippage tolerance
|
||||||
</TYPE.black>
|
</TYPE.black>
|
||||||
<QuestionHelper text="Your transaction will revert if the price changes unfavorably by more than this percentage." />
|
<QuestionHelper text="Your transaction will revert if the price changes unfavorably by more than this percentage." />
|
||||||
</RowFixed>
|
</RowFixed>
|
||||||
|
|
||||||
<SlippageSelector>
|
|
||||||
<RowBetween>
|
<RowBetween>
|
||||||
<Option
|
<Option
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -220,16 +215,16 @@ export default function SlippageTabs({ rawSlippage, setRawSlippage, deadline, se
|
|||||||
: 'Your transaction may be frontrun'}
|
: 'Your transaction may be frontrun'}
|
||||||
</RowBetween>
|
</RowBetween>
|
||||||
)}
|
)}
|
||||||
</SlippageSelector>
|
</AutoColumn>
|
||||||
|
|
||||||
<AutoColumn gap="sm">
|
<AutoColumn gap="sm">
|
||||||
<RowFixed padding={'0 20px'}>
|
<RowFixed>
|
||||||
<TYPE.black fontSize={14} fontWeight={400} color={theme.text2}>
|
<TYPE.black fontSize={14} fontWeight={400} color={theme.text2}>
|
||||||
Deadline
|
Transaction deadline
|
||||||
</TYPE.black>
|
</TYPE.black>
|
||||||
<QuestionHelper text="Your transaction will revert if it is pending for more than this long." />
|
<QuestionHelper text="Your transaction will revert if it is pending for more than this long." />
|
||||||
</RowFixed>
|
</RowFixed>
|
||||||
<RowFixed padding={'0 20px'}>
|
<RowFixed>
|
||||||
<OptionCustom style={{ width: '80px' }} tabIndex={-1}>
|
<OptionCustom style={{ width: '80px' }} tabIndex={-1}>
|
||||||
<Input
|
<Input
|
||||||
color={!!deadlineError ? 'red' : undefined}
|
color={!!deadlineError ? 'red' : undefined}
|
||||||
@ -246,6 +241,6 @@ export default function SlippageTabs({ rawSlippage, setRawSlippage, deadline, se
|
|||||||
</TYPE.body>
|
</TYPE.body>
|
||||||
</RowFixed>
|
</RowFixed>
|
||||||
</AutoColumn>
|
</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 { SectionBreak } from './styleds'
|
||||||
import QuestionHelper from '../QuestionHelper'
|
import QuestionHelper from '../QuestionHelper'
|
||||||
import { RowBetween, RowFixed } from '../Row'
|
import { RowBetween, RowFixed } from '../Row'
|
||||||
import SlippageTabs, { SlippageTabsProps } from '../SlippageTabs'
|
|
||||||
import FormattedPriceImpact from './FormattedPriceImpact'
|
import FormattedPriceImpact from './FormattedPriceImpact'
|
||||||
import TokenLogo from '../TokenLogo'
|
import TokenLogo from '../TokenLogo'
|
||||||
import flatMap from 'lodash.flatmap'
|
import flatMap from 'lodash.flatmap'
|
||||||
|
import { useUserSlippageTolerance } from '../../state/user/hooks'
|
||||||
|
|
||||||
function TradeSummary({ trade, allowedSlippage }: { trade: Trade; allowedSlippage: number }) {
|
function TradeSummary({ trade, allowedSlippage }: { trade: Trade; allowedSlippage: number }) {
|
||||||
const theme = useContext(ThemeContext)
|
const theme = useContext(ThemeContext)
|
||||||
@ -61,20 +61,20 @@ function TradeSummary({ trade, allowedSlippage }: { trade: Trade; allowedSlippag
|
|||||||
</TYPE.black>
|
</TYPE.black>
|
||||||
</RowBetween>
|
</RowBetween>
|
||||||
</AutoColumn>
|
</AutoColumn>
|
||||||
|
|
||||||
<SectionBreak />
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdvancedSwapDetailsProps extends SlippageTabsProps {
|
export interface AdvancedSwapDetailsProps {
|
||||||
trade?: Trade
|
trade?: Trade
|
||||||
onDismiss: () => void
|
onDismiss: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AdvancedSwapDetails({ trade, onDismiss, ...slippageTabProps }: AdvancedSwapDetailsProps) {
|
export function AdvancedSwapDetails({ trade, onDismiss }: AdvancedSwapDetailsProps) {
|
||||||
const theme = useContext(ThemeContext)
|
const theme = useContext(ThemeContext)
|
||||||
|
|
||||||
|
const [allowedSlippage] = useUserSlippageTolerance()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AutoColumn gap="md">
|
<AutoColumn gap="md">
|
||||||
<CursorPointer>
|
<CursorPointer>
|
||||||
@ -85,13 +85,9 @@ export function AdvancedSwapDetails({ trade, onDismiss, ...slippageTabProps }: A
|
|||||||
<ChevronUp color={theme.text2} />
|
<ChevronUp color={theme.text2} />
|
||||||
</RowBetween>
|
</RowBetween>
|
||||||
</CursorPointer>
|
</CursorPointer>
|
||||||
|
|
||||||
<SectionBreak />
|
<SectionBreak />
|
||||||
|
{trade && <TradeSummary trade={trade} allowedSlippage={allowedSlippage} />}
|
||||||
{trade && <TradeSummary trade={trade} allowedSlippage={slippageTabProps.rawSlippage} />}
|
{trade?.route?.path?.length > 2 && <SectionBreak />}
|
||||||
|
|
||||||
<SlippageTabs {...slippageTabProps} />
|
|
||||||
|
|
||||||
{trade?.route?.path?.length > 2 && (
|
{trade?.route?.path?.length > 2 && (
|
||||||
<AutoColumn style={{ padding: '0 20px' }}>
|
<AutoColumn style={{ padding: '0 20px' }}>
|
||||||
<RowFixed>
|
<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;
|
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 }) =>
|
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)`
|
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_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_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%
|
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
|
// 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%
|
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
|
// and the user has approved the slippage adjusted input amount for the trade
|
||||||
export function useSwapCallback(
|
export function useSwapCallback(
|
||||||
trade?: Trade, // trade to execute, required
|
trade?: Trade, // trade to execute, required
|
||||||
allowedSlippage: number = INITIAL_ALLOWED_SLIPPAGE, // in bips, optional
|
allowedSlippage: number = INITIAL_ALLOWED_SLIPPAGE, // in bips
|
||||||
deadline: number = DEFAULT_DEADLINE_FROM_NOW, // in seconds from now, optional
|
deadline: number = DEFAULT_DEADLINE_FROM_NOW, // in seconds from now
|
||||||
to?: string // recipient of output, optional
|
to?: string // recipient of output, optional
|
||||||
): null | (() => Promise<string>) {
|
): null | (() => Promise<string>) {
|
||||||
const { account, chainId, library } = useActiveWeb3React()
|
const { account, chainId, library } = useActiveWeb3React()
|
||||||
|
@ -17,7 +17,7 @@ import Row, { AutoRow, RowBetween, RowFixed, RowFlat } from '../../components/Ro
|
|||||||
|
|
||||||
import TokenLogo from '../../components/TokenLogo'
|
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 { useActiveWeb3React } from '../../hooks'
|
||||||
|
|
||||||
import { useTransactionAdder } from '../../state/transactions/hooks'
|
import { useTransactionAdder } from '../../state/transactions/hooks'
|
||||||
@ -34,7 +34,7 @@ import {
|
|||||||
import { Field } from '../../state/mint/actions'
|
import { Field } from '../../state/mint/actions'
|
||||||
import { useApproveCallback, ApprovalState } from '../../hooks/useApproveCallback'
|
import { useApproveCallback, ApprovalState } from '../../hooks/useApproveCallback'
|
||||||
import { useWalletModalToggle } from '../../state/application/hooks'
|
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 }>) {
|
export default function AddLiquidity({ match: { params } }: RouteComponentProps<{ tokens: string }>) {
|
||||||
useDefaultsFromURLMatchParams(params)
|
useDefaultsFromURLMatchParams(params)
|
||||||
@ -45,6 +45,8 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
|
|||||||
// toggle wallet when disconnected
|
// toggle wallet when disconnected
|
||||||
const toggleWalletModal = useWalletModalToggle()
|
const toggleWalletModal = useWalletModalToggle()
|
||||||
|
|
||||||
|
const expertMode = useIsExpertMode()
|
||||||
|
|
||||||
// mint state
|
// mint state
|
||||||
const { independentField, typedValue, otherTypedValue } = useMintState()
|
const { independentField, typedValue, otherTypedValue } = useMintState()
|
||||||
const {
|
const {
|
||||||
@ -64,14 +66,13 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
|
|||||||
const isValid = !error
|
const isValid = !error
|
||||||
|
|
||||||
// modal and loading
|
// modal and loading
|
||||||
const [showAdvanced, setShowAdvanced] = useState<boolean>(false) // toggling slippage, deadline, etc. on and off
|
const [showConfirm, setShowConfirm] = useState<boolean>(false)
|
||||||
const [showConfirm, setShowConfirm] = useState<boolean>(false) // show confirmation modal
|
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirm
|
||||||
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // waiting for user confirmaion/rejection
|
|
||||||
const [txHash, setTxHash] = useState<string>('')
|
|
||||||
|
|
||||||
// tx parameters
|
// txn values
|
||||||
const [deadline, setDeadline] = useState<number>(DEFAULT_DEADLINE_FROM_NOW)
|
const [deadline] = useUserDeadline() // custom from users settings
|
||||||
const [allowedSlippage, setAllowedSlippage] = useState<number>(INITIAL_ALLOWED_SLIPPAGE)
|
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
|
||||||
|
const [txHash, setTxHash] = useState<string>('')
|
||||||
|
|
||||||
// get formatted amounts
|
// get formatted amounts
|
||||||
const formattedAmounts = {
|
const formattedAmounts = {
|
||||||
@ -427,7 +428,7 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
|
|||||||
)}
|
)}
|
||||||
<ButtonError
|
<ButtonError
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowConfirm(true)
|
expertMode ? onAdd() : setShowConfirm(true)
|
||||||
}}
|
}}
|
||||||
disabled={!isValid || approvalA !== ApprovalState.APPROVED || approvalB !== ApprovalState.APPROVED}
|
disabled={!isValid || approvalA !== ApprovalState.APPROVED || approvalB !== ApprovalState.APPROVED}
|
||||||
error={!isValid && !!parsedAmounts[Field.TOKEN_A] && !!parsedAmounts[Field.TOKEN_B]}
|
error={!isValid && !!parsedAmounts[Field.TOKEN_A] && !!parsedAmounts[Field.TOKEN_B]}
|
||||||
@ -442,17 +443,6 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
|
|||||||
</Wrapper>
|
</Wrapper>
|
||||||
</AppBody>
|
</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 ? (
|
{pair && !noLiquidity ? (
|
||||||
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
|
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
|
||||||
<PositionCard pair={pair} minimal={true} />
|
<PositionCard pair={pair} minimal={true} />
|
||||||
|
@ -18,7 +18,7 @@ import Row, { RowBetween, RowFixed } from '../../components/Row'
|
|||||||
|
|
||||||
import Slider from '../../components/Slider'
|
import Slider from '../../components/Slider'
|
||||||
import TokenLogo from '../../components/TokenLogo'
|
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 { useActiveWeb3React } from '../../hooks'
|
||||||
import { usePairContract } from '../../hooks/useContract'
|
import { usePairContract } from '../../hooks/useContract'
|
||||||
|
|
||||||
@ -31,9 +31,9 @@ import { useApproveCallback, ApprovalState } from '../../hooks/useApproveCallbac
|
|||||||
import { Dots } from '../../components/swap/styleds'
|
import { Dots } from '../../components/swap/styleds'
|
||||||
import { useDefaultsFromURLMatchParams, useBurnActionHandlers } from '../../state/burn/hooks'
|
import { useDefaultsFromURLMatchParams, useBurnActionHandlers } from '../../state/burn/hooks'
|
||||||
import { useDerivedBurnInfo, useBurnState } from '../../state/burn/hooks'
|
import { useDerivedBurnInfo, useBurnState } from '../../state/burn/hooks'
|
||||||
import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetailsDropdown'
|
|
||||||
import { Field } from '../../state/burn/actions'
|
import { Field } from '../../state/burn/actions'
|
||||||
import { useWalletModalToggle } from '../../state/application/hooks'
|
import { useWalletModalToggle } from '../../state/application/hooks'
|
||||||
|
import { useUserDeadline, useUserSlippageTolerance } from '../../state/user/hooks'
|
||||||
import { BigNumber } from '@ethersproject/bignumber'
|
import { BigNumber } from '@ethersproject/bignumber'
|
||||||
|
|
||||||
export default function RemoveLiquidity({ match: { params } }: RouteComponentProps<{ tokens: string }>) {
|
export default function RemoveLiquidity({ match: { params } }: RouteComponentProps<{ tokens: string }>) {
|
||||||
@ -52,15 +52,14 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
|
|||||||
const isValid = !error
|
const isValid = !error
|
||||||
|
|
||||||
// modal and loading
|
// modal and loading
|
||||||
const [showDetailed, setShowDetailed] = useState<boolean>(false) // toggling detailed view
|
const [showConfirm, setShowConfirm] = useState<boolean>(false)
|
||||||
const [showAdvanced, setShowAdvanced] = useState<boolean>(false) // toggling slippage, deadline, etc. on and off
|
const [showDetailed, setShowDetailed] = useState<boolean>(false)
|
||||||
const [showConfirm, setShowConfirm] = useState<boolean>(false) // show confirmation modal
|
const [attemptingTxn, setAttemptingTxn] = useState(false) // clicked confirm
|
||||||
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // waiting for user confirmaion/rejection
|
|
||||||
const [txHash, setTxHash] = useState<string>('')
|
|
||||||
|
|
||||||
// tx parameters
|
// txn values
|
||||||
const [deadline, setDeadline] = useState<number>(DEFAULT_DEADLINE_FROM_NOW)
|
const [txHash, setTxHash] = useState<string>('')
|
||||||
const [allowedSlippage, setAllowedSlippage] = useState<number>(INITIAL_ALLOWED_SLIPPAGE)
|
const [deadline] = useUserDeadline()
|
||||||
|
const [allowedSlippage] = useUserSlippageTolerance()
|
||||||
|
|
||||||
const formattedAmounts = {
|
const formattedAmounts = {
|
||||||
[Field.LIQUIDITY_PERCENT]: parsedAmounts[Field.LIQUIDITY_PERCENT].equalTo('0')
|
[Field.LIQUIDITY_PERCENT]: parsedAmounts[Field.LIQUIDITY_PERCENT].equalTo('0')
|
||||||
@ -592,17 +591,6 @@ export default function RemoveLiquidity({ match: { params } }: RouteComponentPro
|
|||||||
</Wrapper>
|
</Wrapper>
|
||||||
</AppBody>
|
</AppBody>
|
||||||
|
|
||||||
{isValid ? (
|
|
||||||
<AdvancedSwapDetailsDropdown
|
|
||||||
rawSlippage={allowedSlippage}
|
|
||||||
deadline={deadline}
|
|
||||||
showAdvanced={showAdvanced}
|
|
||||||
setShowAdvanced={setShowAdvanced}
|
|
||||||
setDeadline={setDeadline}
|
|
||||||
setRawSlippage={setAllowedSlippage}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{pair ? (
|
{pair ? (
|
||||||
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
|
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
|
||||||
<PositionCard pair={pair} minimal={true} />
|
<PositionCard pair={pair} minimal={true} />
|
||||||
|
@ -22,19 +22,14 @@ import { TransferModalHeader } from '../../components/swap/TransferModalHeader'
|
|||||||
import BetterTradeLink from '../../components/swap/BetterTradeLink'
|
import BetterTradeLink from '../../components/swap/BetterTradeLink'
|
||||||
import TokenLogo from '../../components/TokenLogo'
|
import TokenLogo from '../../components/TokenLogo'
|
||||||
import { TokenWarningCards } from '../../components/TokenWarningCard'
|
import { TokenWarningCards } from '../../components/TokenWarningCard'
|
||||||
import {
|
import { INITIAL_ALLOWED_SLIPPAGE, MIN_ETH, BETTER_TRADE_LINK_THRESHOLD } from '../../constants'
|
||||||
DEFAULT_DEADLINE_FROM_NOW,
|
|
||||||
INITIAL_ALLOWED_SLIPPAGE,
|
|
||||||
MIN_ETH,
|
|
||||||
BETTER_TRADE_LINK_THRESHOLD
|
|
||||||
} from '../../constants'
|
|
||||||
import { getTradeVersion, isTradeBetter } from '../../data/V1'
|
import { getTradeVersion, isTradeBetter } from '../../data/V1'
|
||||||
import { useActiveWeb3React } from '../../hooks'
|
import { useActiveWeb3React } from '../../hooks'
|
||||||
import { useApproveCallbackFromTrade, ApprovalState } from '../../hooks/useApproveCallback'
|
import { useApproveCallbackFromTrade, ApprovalState } from '../../hooks/useApproveCallback'
|
||||||
import { useSendCallback } from '../../hooks/useSendCallback'
|
import { useSendCallback } from '../../hooks/useSendCallback'
|
||||||
import { useSwapCallback } from '../../hooks/useSwapCallback'
|
import { useSwapCallback } from '../../hooks/useSwapCallback'
|
||||||
|
import { useWalletModalToggle, useToggleSettingsMenu } from '../../state/application/hooks'
|
||||||
import useToggledVersion, { Version } from '../../hooks/useToggledVersion'
|
import useToggledVersion, { Version } from '../../hooks/useToggledVersion'
|
||||||
import { useWalletModalToggle } from '../../state/application/hooks'
|
|
||||||
import { Field } from '../../state/swap/actions'
|
import { Field } from '../../state/swap/actions'
|
||||||
import {
|
import {
|
||||||
useDefaultsFromURLSearch,
|
useDefaultsFromURLSearch,
|
||||||
@ -46,7 +41,8 @@ import { useAllTokenBalancesTreatingWETHasETH } from '../../state/wallet/hooks'
|
|||||||
import { CursorPointer, TYPE } from '../../theme'
|
import { CursorPointer, TYPE } from '../../theme'
|
||||||
import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown, warningSeverity } from '../../utils/prices'
|
import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown, warningSeverity } from '../../utils/prices'
|
||||||
import AppBody from '../AppBody'
|
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() {
|
export default function Send() {
|
||||||
useDefaultsFromURLSearch()
|
useDefaultsFromURLSearch()
|
||||||
@ -59,6 +55,10 @@ export default function Send() {
|
|||||||
// toggle wallet when disconnected
|
// toggle wallet when disconnected
|
||||||
const toggleWalletModal = useWalletModalToggle()
|
const toggleWalletModal = useWalletModalToggle()
|
||||||
|
|
||||||
|
// for expert mode
|
||||||
|
const toggleSettings = useToggleSettingsMenu()
|
||||||
|
const [expertMode] = useExpertModeManager()
|
||||||
|
|
||||||
// sending state
|
// sending state
|
||||||
const [sendingWithSwap, setSendingWithSwap] = useState<boolean>(false)
|
const [sendingWithSwap, setSendingWithSwap] = useState<boolean>(false)
|
||||||
const [recipient, setRecipient] = useState<string>('')
|
const [recipient, setRecipient] = useState<string>('')
|
||||||
@ -102,10 +102,8 @@ export default function Send() {
|
|||||||
const [showConfirm, setShowConfirm] = useState<boolean>(false) // show confirmation modal
|
const [showConfirm, setShowConfirm] = useState<boolean>(false) // show confirmation modal
|
||||||
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // waiting for user confirmaion/rejection
|
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // waiting for user confirmaion/rejection
|
||||||
const [txHash, setTxHash] = useState<string>('')
|
const [txHash, setTxHash] = useState<string>('')
|
||||||
|
const [deadline] = useUserDeadline() // custom from user settings
|
||||||
// tx parameters
|
const [allowedSlippage] = useUserSlippageTolerance() // custom from user settings
|
||||||
const [deadline, setDeadline] = useState<number>(DEFAULT_DEADLINE_FROM_NOW)
|
|
||||||
const [allowedSlippage, setAllowedSlippage] = useState<number>(INITIAL_ALLOWED_SLIPPAGE)
|
|
||||||
|
|
||||||
const route = bestTrade?.route
|
const route = bestTrade?.route
|
||||||
const userHasSpecifiedInputOutput =
|
const userHasSpecifiedInputOutput =
|
||||||
@ -223,7 +221,8 @@ export default function Send() {
|
|||||||
((sendingWithSwap && isSwapValid) || (!sendingWithSwap && isSendValid)) &&
|
((sendingWithSwap && isSwapValid) || (!sendingWithSwap && isSendValid)) &&
|
||||||
(approval === ApprovalState.NOT_APPROVED ||
|
(approval === ApprovalState.NOT_APPROVED ||
|
||||||
approval === ApprovalState.PENDING ||
|
approval === ApprovalState.PENDING ||
|
||||||
(approvalSubmitted && approval === ApprovalState.APPROVED))
|
(approvalSubmitted && approval === ApprovalState.APPROVED)) &&
|
||||||
|
!(severity > 3 && !expertMode)
|
||||||
|
|
||||||
function modalHeader() {
|
function modalHeader() {
|
||||||
if (!sendingWithSwap) {
|
if (!sendingWithSwap) {
|
||||||
@ -487,6 +486,20 @@ export default function Send() {
|
|||||||
<TradePrice showInverted={showInverted} setShowInverted={setShowInverted} trade={bestTrade} />
|
<TradePrice showInverted={showInverted} setShowInverted={setShowInverted} trade={bestTrade} />
|
||||||
</RowBetween>
|
</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 && (
|
{bestTrade && severity > 1 && (
|
||||||
<RowBetween>
|
<RowBetween>
|
||||||
<TYPE.main
|
<TYPE.main
|
||||||
@ -536,7 +549,7 @@ export default function Send() {
|
|||||||
</ButtonPrimary>
|
</ButtonPrimary>
|
||||||
<ButtonError
|
<ButtonError
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowConfirm(true)
|
expertMode ? (sendingWithSwap ? onSwap() : onSend()) : setShowConfirm(true)
|
||||||
}}
|
}}
|
||||||
width="48%"
|
width="48%"
|
||||||
id="send-button"
|
id="send-button"
|
||||||
@ -544,23 +557,28 @@ export default function Send() {
|
|||||||
error={sendingWithSwap && isSwapValid && severity > 2}
|
error={sendingWithSwap && isSwapValid && severity > 2}
|
||||||
>
|
>
|
||||||
<Text fontSize={16} fontWeight={500}>
|
<Text fontSize={16} fontWeight={500}>
|
||||||
{`Send${severity > 2 ? ' Anyway' : ''}`}
|
{severity > 3 && !expertMode ? `Price Impact High` : `Send${severity > 2 ? ' Anyway' : ''}`}
|
||||||
</Text>
|
</Text>
|
||||||
</ButtonError>
|
</ButtonError>
|
||||||
</RowBetween>
|
</RowBetween>
|
||||||
) : (
|
) : (
|
||||||
<ButtonError
|
<ButtonError
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowConfirm(true)
|
expertMode ? (sendingWithSwap ? onSwap() : onSend()) : setShowConfirm(true)
|
||||||
}}
|
}}
|
||||||
id="send-button"
|
id="send-button"
|
||||||
disabled={(sendingWithSwap && !isSwapValid) || (!sendingWithSwap && !isSendValid)}
|
disabled={
|
||||||
|
(sendingWithSwap && !isSwapValid) ||
|
||||||
|
(!sendingWithSwap && !isSendValid) ||
|
||||||
|
(severity > 3 && !expertMode && sendingWithSwap)
|
||||||
|
}
|
||||||
error={sendingWithSwap && isSwapValid && severity > 2}
|
error={sendingWithSwap && isSwapValid && severity > 2}
|
||||||
>
|
>
|
||||||
<Text fontSize={20} fontWeight={500}>
|
<Text fontSize={20} fontWeight={500}>
|
||||||
{(sendingWithSwap ? swapError : null) ||
|
{(sendingWithSwap ? swapError : null) ||
|
||||||
sendAmountError ||
|
sendAmountError ||
|
||||||
recipientError ||
|
recipientError ||
|
||||||
|
(severity > 3 && !expertMode && `Price Impact Too High`) ||
|
||||||
`Send${severity > 2 ? ' Anyway' : ''}`}
|
`Send${severity > 2 ? ' Anyway' : ''}`}
|
||||||
</Text>
|
</Text>
|
||||||
</ButtonError>
|
</ButtonError>
|
||||||
@ -571,21 +589,7 @@ export default function Send() {
|
|||||||
</AppBody>
|
</AppBody>
|
||||||
|
|
||||||
{bestTrade && (
|
{bestTrade && (
|
||||||
<AdvancedSwapDetailsDropdown
|
<AdvancedSwapDetailsDropdown trade={bestTrade} showAdvanced={showAdvanced} setShowAdvanced={setShowAdvanced} />
|
||||||
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>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -20,18 +20,15 @@ import SwapModalHeader from '../../components/swap/SwapModalHeader'
|
|||||||
import TradePrice from '../../components/swap/TradePrice'
|
import TradePrice from '../../components/swap/TradePrice'
|
||||||
import BetterTradeLink from '../../components/swap/BetterTradeLink'
|
import BetterTradeLink from '../../components/swap/BetterTradeLink'
|
||||||
import { TokenWarningCards } from '../../components/TokenWarningCard'
|
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 { useActiveWeb3React } from '../../hooks'
|
||||||
import { useApproveCallbackFromTrade, ApprovalState } from '../../hooks/useApproveCallback'
|
import { useApproveCallbackFromTrade, ApprovalState } from '../../hooks/useApproveCallback'
|
||||||
import { useSwapCallback } from '../../hooks/useSwapCallback'
|
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 useToggledVersion, { Version } from '../../hooks/useToggledVersion'
|
||||||
import { useWalletModalToggle } from '../../state/application/hooks'
|
|
||||||
import { Field } from '../../state/swap/actions'
|
import { Field } from '../../state/swap/actions'
|
||||||
import {
|
import {
|
||||||
useDefaultsFromURLSearch,
|
useDefaultsFromURLSearch,
|
||||||
@ -42,7 +39,7 @@ import {
|
|||||||
import { CursorPointer, TYPE } from '../../theme'
|
import { CursorPointer, TYPE } from '../../theme'
|
||||||
import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown, warningSeverity } from '../../utils/prices'
|
import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown, warningSeverity } from '../../utils/prices'
|
||||||
import AppBody from '../AppBody'
|
import AppBody from '../AppBody'
|
||||||
import { PriceSlippageWarningCard } from '../../components/swap/PriceSlippageWarningCard'
|
import { ClickableText } from '../Pool/styleds'
|
||||||
|
|
||||||
export default function Swap() {
|
export default function Swap() {
|
||||||
useDefaultsFromURLSearch()
|
useDefaultsFromURLSearch()
|
||||||
@ -53,6 +50,14 @@ export default function Swap() {
|
|||||||
// toggle wallet when disconnected
|
// toggle wallet when disconnected
|
||||||
const toggleWalletModal = useWalletModalToggle()
|
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
|
// swap state
|
||||||
const { independentField, typedValue } = useSwapState()
|
const { independentField, typedValue } = useSwapState()
|
||||||
const { bestTrade: bestTradeV2, tokenBalances, parsedAmount, tokens, error, v1Trade } = useDerivedSwapInfo()
|
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 [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // waiting for user confirmaion/rejection
|
||||||
const [txHash, setTxHash] = useState<string>('')
|
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 = {
|
const formattedAmounts = {
|
||||||
[independentField]: typedValue,
|
[independentField]: typedValue,
|
||||||
[dependentField]: parsedAmounts[dependentField] ? parsedAmounts[dependentField].toSignificant(6) : ''
|
[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
|
// check if user has gone through approval process, used to show two step buttons, reset on token change
|
||||||
const [approvalSubmitted, setApprovalSubmitted] = useState<boolean>(false)
|
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
|
// mark when a user has submitted an approval, reset onTokenSelection for input field
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (approval === ApprovalState.PENDING) {
|
if (approval === ApprovalState.PENDING) {
|
||||||
@ -146,7 +140,6 @@ export default function Swap() {
|
|||||||
if (priceImpactWithoutFee && !confirmPriceImpactWithoutFee(priceImpactWithoutFee)) {
|
if (priceImpactWithoutFee && !confirmPriceImpactWithoutFee(priceImpactWithoutFee)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setAttemptingTxn(true)
|
setAttemptingTxn(true)
|
||||||
swapCallback()
|
swapCallback()
|
||||||
.then(hash => {
|
.then(hash => {
|
||||||
@ -178,6 +171,15 @@ export default function Swap() {
|
|||||||
// warnings on slippage
|
// warnings on slippage
|
||||||
const priceImpactSeverity = warningSeverity(priceImpactWithoutFee)
|
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() {
|
function modalHeader() {
|
||||||
return (
|
return (
|
||||||
<SwapModalHeader
|
<SwapModalHeader
|
||||||
@ -292,6 +294,17 @@ export default function Swap() {
|
|||||||
<TradePrice trade={bestTrade} showInverted={showInverted} setShowInverted={setShowInverted} />
|
<TradePrice trade={bestTrade} showInverted={showInverted} setShowInverted={setShowInverted} />
|
||||||
</RowBetween>
|
</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 && (
|
{bestTrade && priceImpactSeverity > 1 && (
|
||||||
<RowBetween>
|
<RowBetween>
|
||||||
<TYPE.main
|
<TYPE.main
|
||||||
@ -335,29 +348,35 @@ export default function Swap() {
|
|||||||
</ButtonPrimary>
|
</ButtonPrimary>
|
||||||
<ButtonError
|
<ButtonError
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowConfirm(true)
|
expertMode ? onSwap() : setShowConfirm(true)
|
||||||
}}
|
}}
|
||||||
width="48%"
|
width="48%"
|
||||||
id="swap-button"
|
id="swap-button"
|
||||||
disabled={!isValid || approval !== ApprovalState.APPROVED}
|
disabled={!isValid || approval !== ApprovalState.APPROVED || (priceImpactSeverity > 3 && !expertMode)}
|
||||||
error={isValid && priceImpactSeverity > 2}
|
error={isValid && priceImpactSeverity > 2}
|
||||||
>
|
>
|
||||||
<Text fontSize={16} fontWeight={500}>
|
<Text fontSize={16} fontWeight={500}>
|
||||||
{`Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
|
{priceImpactSeverity > 3 && !expertMode
|
||||||
|
? `Price Impact High`
|
||||||
|
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
|
||||||
</Text>
|
</Text>
|
||||||
</ButtonError>
|
</ButtonError>
|
||||||
</RowBetween>
|
</RowBetween>
|
||||||
) : (
|
) : (
|
||||||
<ButtonError
|
<ButtonError
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowConfirm(true)
|
expertMode ? onSwap() : setShowConfirm(true)
|
||||||
}}
|
}}
|
||||||
id="swap-button"
|
id="swap-button"
|
||||||
disabled={!isValid}
|
disabled={!isValid || (priceImpactSeverity > 3 && !expertMode)}
|
||||||
error={isValid && priceImpactSeverity > 2}
|
error={isValid && priceImpactSeverity > 2}
|
||||||
>
|
>
|
||||||
<Text fontSize={20} fontWeight={500}>
|
<Text fontSize={20} fontWeight={500}>
|
||||||
{error ?? `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
|
{error
|
||||||
|
? error
|
||||||
|
: priceImpactSeverity > 3 && !expertMode
|
||||||
|
? `Price Impact Too High`
|
||||||
|
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
|
||||||
</Text>
|
</Text>
|
||||||
</ButtonError>
|
</ButtonError>
|
||||||
)}
|
)}
|
||||||
@ -367,21 +386,7 @@ export default function Swap() {
|
|||||||
</AppBody>
|
</AppBody>
|
||||||
|
|
||||||
{bestTrade && (
|
{bestTrade && (
|
||||||
<AdvancedSwapDetailsDropdown
|
<AdvancedSwapDetailsDropdown trade={bestTrade} showAdvanced={showAdvanced} setShowAdvanced={setShowAdvanced} />
|
||||||
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>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -23,5 +23,6 @@ export type PopupContent =
|
|||||||
|
|
||||||
export const updateBlockNumber = createAction<{ chainId: number; blockNumber: number }>('updateBlockNumber')
|
export const updateBlockNumber = createAction<{ chainId: number; blockNumber: number }>('updateBlockNumber')
|
||||||
export const toggleWalletModal = createAction<void>('toggleWalletModal')
|
export const toggleWalletModal = createAction<void>('toggleWalletModal')
|
||||||
|
export const toggleSettingsMenu = createAction<void>('toggleSettingsMenu')
|
||||||
export const addPopup = createAction<{ key?: string; content: PopupContent }>('addPopup')
|
export const addPopup = createAction<{ key?: string; content: PopupContent }>('addPopup')
|
||||||
export const removePopup = createAction<{ key: string }>('removePopup')
|
export const removePopup = createAction<{ key: string }>('removePopup')
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useCallback, useMemo } from 'react'
|
import { useCallback, useMemo } from 'react'
|
||||||
import { useActiveWeb3React } from '../../hooks'
|
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 { useSelector, useDispatch } from 'react-redux'
|
||||||
import { AppState } from '../index'
|
import { AppState } from '../index'
|
||||||
|
|
||||||
@ -19,6 +19,15 @@ export function useWalletModalToggle(): () => void {
|
|||||||
return useCallback(() => dispatch(toggleWalletModal()), [dispatch])
|
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
|
// returns a function that allows adding a popup
|
||||||
export function useAddPopup(): (content: PopupContent, key?: string) => void {
|
export function useAddPopup(): (content: PopupContent, key?: string) => void {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
import { createReducer, nanoid } from '@reduxjs/toolkit'
|
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 }>
|
type PopupList = Array<{ key: string; show: boolean; content: PopupContent }>
|
||||||
|
|
||||||
@ -7,12 +14,14 @@ interface ApplicationState {
|
|||||||
blockNumber: { [chainId: number]: number }
|
blockNumber: { [chainId: number]: number }
|
||||||
popupList: PopupList
|
popupList: PopupList
|
||||||
walletModalOpen: boolean
|
walletModalOpen: boolean
|
||||||
|
settingsMenuOpen: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: ApplicationState = {
|
const initialState: ApplicationState = {
|
||||||
blockNumber: {},
|
blockNumber: {},
|
||||||
popupList: [],
|
popupList: [],
|
||||||
walletModalOpen: false
|
walletModalOpen: false,
|
||||||
|
settingsMenuOpen: false
|
||||||
}
|
}
|
||||||
|
|
||||||
export default createReducer(initialState, builder =>
|
export default createReducer(initialState, builder =>
|
||||||
@ -28,6 +37,9 @@ export default createReducer(initialState, builder =>
|
|||||||
.addCase(toggleWalletModal, state => {
|
.addCase(toggleWalletModal, state => {
|
||||||
state.walletModalOpen = !state.walletModalOpen
|
state.walletModalOpen = !state.walletModalOpen
|
||||||
})
|
})
|
||||||
|
.addCase(toggleSettingsMenu, state => {
|
||||||
|
state.settingsMenuOpen = !state.settingsMenuOpen
|
||||||
|
})
|
||||||
.addCase(addPopup, (state, { payload: { content, key } }) => {
|
.addCase(addPopup, (state, { payload: { content, key } }) => {
|
||||||
if (key && state.popupList.some(popup => popup.key === key)) return
|
if (key && state.popupList.some(popup => popup.key === key)) return
|
||||||
state.popupList.push({
|
state.popupList.push({
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { Version } from './../../hooks/useToggledVersion'
|
||||||
import { parseUnits } from '@ethersproject/units'
|
import { parseUnits } from '@ethersproject/units'
|
||||||
import { ChainId, JSBI, Token, TokenAmount, Trade, WETH } from '@uniswap/sdk'
|
import { ChainId, JSBI, Token, TokenAmount, Trade, WETH } from '@uniswap/sdk'
|
||||||
import { ParsedQs } from 'qs'
|
import { ParsedQs } from 'qs'
|
||||||
@ -13,6 +14,9 @@ import { AppDispatch, AppState } from '../index'
|
|||||||
import { useTokenBalancesTreatWETHAsETH } from '../wallet/hooks'
|
import { useTokenBalancesTreatWETHAsETH } from '../wallet/hooks'
|
||||||
import { Field, replaceSwapState, selectToken, switchTokens, typeInput } from './actions'
|
import { Field, replaceSwapState, selectToken, switchTokens, typeInput } from './actions'
|
||||||
import { SwapState } from './reducer'
|
import { SwapState } from './reducer'
|
||||||
|
import useToggledVersion from '../../hooks/useToggledVersion'
|
||||||
|
import { useUserSlippageTolerance } from '../user/hooks'
|
||||||
|
import { computeSlippageAdjustedAmounts } from '../../utils/prices'
|
||||||
|
|
||||||
export function useSwapState(): AppState['swap'] {
|
export function useSwapState(): AppState['swap'] {
|
||||||
return useSelector<AppState, AppState['swap']>(state => state.swap)
|
return useSelector<AppState, AppState['swap']>(state => state.swap)
|
||||||
@ -83,6 +87,8 @@ export function useDerivedSwapInfo(): {
|
|||||||
} {
|
} {
|
||||||
const { account } = useActiveWeb3React()
|
const { account } = useActiveWeb3React()
|
||||||
|
|
||||||
|
const toggledVersion = useToggledVersion()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
independentField,
|
independentField,
|
||||||
typedValue,
|
typedValue,
|
||||||
@ -132,12 +138,29 @@ export function useDerivedSwapInfo(): {
|
|||||||
error = error ?? 'Select a token'
|
error = error ?? 'Select a token'
|
||||||
}
|
}
|
||||||
|
|
||||||
// this check is incorrect, it should check against the maximum amount in
|
const [allowedSlippage] = useUserSlippageTolerance()
|
||||||
// rather than the estimated amount in
|
|
||||||
// const [balanceIn, amountIn] = [tokenBalances[Field.INPUT], parsedAmounts[Field.INPUT]]
|
const slippageAdjustedAmounts =
|
||||||
// if (balanceIn && amountIn && balanceIn.lessThan(amountIn)) {
|
bestTrade && allowedSlippage && computeSlippageAdjustedAmounts(bestTrade, allowedSlippage)
|
||||||
// error = 'Insufficient ' + amountIn.token.symbol + ' balance'
|
|
||||||
// }
|
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 {
|
return {
|
||||||
tokens,
|
tokens,
|
||||||
|
@ -16,6 +16,11 @@ export interface SerializedPair {
|
|||||||
export const updateVersion = createAction<void>('updateVersion')
|
export const updateVersion = createAction<void>('updateVersion')
|
||||||
export const updateMatchesDarkMode = createAction<{ matchesDarkMode: boolean }>('updateMatchesDarkMode')
|
export const updateMatchesDarkMode = createAction<{ matchesDarkMode: boolean }>('updateMatchesDarkMode')
|
||||||
export const updateUserDarkMode = createAction<{ userDarkMode: boolean }>('updateUserDarkMode')
|
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 addSerializedToken = createAction<{ serializedToken: SerializedToken }>('addSerializedToken')
|
||||||
export const removeSerializedToken = createAction<{ chainId: number; address: string }>('removeSerializedToken')
|
export const removeSerializedToken = createAction<{ chainId: number; address: string }>('removeSerializedToken')
|
||||||
export const addSerializedPair = createAction<{ serializedPair: SerializedPair }>('addSerializedPair')
|
export const addSerializedPair = createAction<{ serializedPair: SerializedPair }>('addSerializedPair')
|
||||||
|
@ -13,7 +13,10 @@ import {
|
|||||||
removeSerializedToken,
|
removeSerializedToken,
|
||||||
SerializedPair,
|
SerializedPair,
|
||||||
SerializedToken,
|
SerializedToken,
|
||||||
updateUserDarkMode
|
updateUserDarkMode,
|
||||||
|
updateUserExpertMode,
|
||||||
|
updateUserSlippageTolerance,
|
||||||
|
updateUserDeadline
|
||||||
} from './actions'
|
} from './actions'
|
||||||
import { BASES_TO_TRACK_LIQUIDITY_FOR, DUMMY_PAIRS_TO_PIN } from '../../constants'
|
import { BASES_TO_TRACK_LIQUIDITY_FOR, DUMMY_PAIRS_TO_PIN } from '../../constants'
|
||||||
|
|
||||||
@ -63,6 +66,54 @@ export function useDarkModeManager(): [boolean, () => void] {
|
|||||||
return [darkMode, toggleSetDarkMode]
|
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 {
|
export function useAddUserToken(): (token: Token) => void {
|
||||||
const dispatch = useDispatch<AppDispatch>()
|
const dispatch = useDispatch<AppDispatch>()
|
||||||
return useCallback(
|
return useCallback(
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
import { INITIAL_ALLOWED_SLIPPAGE, DEFAULT_DEADLINE_FROM_NOW } from './../../constants/index'
|
||||||
import { createReducer } from '@reduxjs/toolkit'
|
import { createReducer } from '@reduxjs/toolkit'
|
||||||
import { ChainId, WETH } from '@uniswap/sdk'
|
|
||||||
import {
|
import {
|
||||||
addSerializedPair,
|
addSerializedPair,
|
||||||
addSerializedToken,
|
addSerializedToken,
|
||||||
@ -10,7 +10,10 @@ import {
|
|||||||
SerializedToken,
|
SerializedToken,
|
||||||
updateMatchesDarkMode,
|
updateMatchesDarkMode,
|
||||||
updateUserDarkMode,
|
updateUserDarkMode,
|
||||||
updateVersion
|
updateVersion,
|
||||||
|
updateUserExpertMode,
|
||||||
|
updateUserSlippageTolerance,
|
||||||
|
updateUserDeadline
|
||||||
} from './actions'
|
} from './actions'
|
||||||
|
|
||||||
const currentTimestamp = () => new Date().getTime()
|
const currentTimestamp = () => new Date().getTime()
|
||||||
@ -21,6 +24,14 @@ interface UserState {
|
|||||||
userDarkMode: boolean | null // the user's choice for dark mode or light mode
|
userDarkMode: boolean | null // the user's choice for dark mode or light mode
|
||||||
matchesDarkMode: boolean // whether the dark mode media query matches
|
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: {
|
tokens: {
|
||||||
[chainId: number]: {
|
[chainId: number]: {
|
||||||
[address: string]: SerializedToken
|
[address: string]: SerializedToken
|
||||||
@ -50,13 +61,13 @@ function pairKey(token0Address: string, token1Address: string) {
|
|||||||
|
|
||||||
const initialState: UserState = {
|
const initialState: UserState = {
|
||||||
lastVersion: '',
|
lastVersion: '',
|
||||||
|
|
||||||
userDarkMode: null,
|
userDarkMode: null,
|
||||||
matchesDarkMode: false,
|
matchesDarkMode: false,
|
||||||
|
userExpertMode: false,
|
||||||
|
userSlippageTolerance: INITIAL_ALLOWED_SLIPPAGE,
|
||||||
|
userDeadline: DEFAULT_DEADLINE_FROM_NOW,
|
||||||
tokens: {},
|
tokens: {},
|
||||||
pairs: {},
|
pairs: {},
|
||||||
|
|
||||||
timestamp: currentTimestamp()
|
timestamp: currentTimestamp()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,12 +79,14 @@ export default createReducer(initialState, builder =>
|
|||||||
if (GIT_COMMIT_HASH && state.lastVersion !== GIT_COMMIT_HASH) {
|
if (GIT_COMMIT_HASH && state.lastVersion !== GIT_COMMIT_HASH) {
|
||||||
state.lastVersion = GIT_COMMIT_HASH
|
state.lastVersion = GIT_COMMIT_HASH
|
||||||
|
|
||||||
// Wed May 20, 2020 @ ~9pm central
|
// slippage isnt being tracked in local storage, reset to default
|
||||||
if (state.timestamp < 1590027589111) {
|
if (typeof state.userSlippageTolerance !== 'number') {
|
||||||
// this should remove the user added token from 'eth' for mainnet
|
state.userSlippageTolerance = INITIAL_ALLOWED_SLIPPAGE
|
||||||
if (state.tokens[ChainId.MAINNET]) {
|
|
||||||
delete state.tokens[ChainId.MAINNET][WETH[ChainId.MAINNET].address]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deadline isnt being tracked in local storage, reset to default
|
||||||
|
if (typeof state.userDeadline !== 'number') {
|
||||||
|
state.userDeadline = DEFAULT_DEADLINE_FROM_NOW
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.timestamp = currentTimestamp()
|
state.timestamp = currentTimestamp()
|
||||||
@ -86,6 +99,18 @@ export default createReducer(initialState, builder =>
|
|||||||
state.matchesDarkMode = action.payload.matchesDarkMode
|
state.matchesDarkMode = action.payload.matchesDarkMode
|
||||||
state.timestamp = currentTimestamp()
|
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 } }) => {
|
.addCase(addSerializedToken, (state, { payload: { serializedToken } }) => {
|
||||||
state.tokens[serializedToken.chainId] = state.tokens[serializedToken.chainId] || {}
|
state.tokens[serializedToken.chainId] = state.tokens[serializedToken.chainId] || {}
|
||||||
state.tokens[serializedToken.chainId][serializedToken.address] = serializedToken
|
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 { Fraction, JSBI, Percent, TokenAmount, Trade } from '@uniswap/sdk'
|
||||||
import { ALLOWED_PRICE_IMPACT_HIGH, ALLOWED_PRICE_IMPACT_LOW, ALLOWED_PRICE_IMPACT_MEDIUM } from '../constants'
|
import { ALLOWED_PRICE_IMPACT_HIGH, ALLOWED_PRICE_IMPACT_LOW, ALLOWED_PRICE_IMPACT_MEDIUM } from '../constants'
|
||||||
import { Field } from '../state/swap/actions'
|
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_HIGH)) return 3
|
||||||
if (!priceImpact?.lessThan(ALLOWED_PRICE_IMPACT_MEDIUM)) return 2
|
if (!priceImpact?.lessThan(ALLOWED_PRICE_IMPACT_MEDIUM)) return 2
|
||||||
if (!priceImpact?.lessThan(ALLOWED_PRICE_IMPACT_LOW)) return 1
|
if (!priceImpact?.lessThan(ALLOWED_PRICE_IMPACT_LOW)) return 1
|
||||||
|
Loading…
Reference in New Issue
Block a user