Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5ff3beb92 | ||
|
|
35ccf425f6 | ||
|
|
fe030412cd | ||
|
|
4d5a43351f | ||
|
|
ac1bc3b3a6 | ||
|
|
d1063d50ed | ||
|
|
46fc74e90f | ||
|
|
2c4f4092d8 | ||
|
|
aac7268dc8 |
1
.github/workflows/release.yaml
vendored
1
.github/workflows/release.yaml
vendored
@@ -10,6 +10,7 @@ on:
|
|||||||
- v2
|
- v2
|
||||||
paths:
|
paths:
|
||||||
- '.github/workflows/release.yaml'
|
- '.github/workflows/release.yaml'
|
||||||
|
- '.env.production'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
bump_version:
|
bump_version:
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ describe('Swap', () => {
|
|||||||
cy.get('#swap-currency-input .token-amount-input').should('be.visible')
|
cy.get('#swap-currency-input .token-amount-input').should('be.visible')
|
||||||
cy.get('#swap-currency-input .token-amount-input').type('0.001', { force: true, delay: 200 })
|
cy.get('#swap-currency-input .token-amount-input').type('0.001', { force: true, delay: 200 })
|
||||||
cy.get('#swap-currency-output .token-amount-input').should('not.equal', '')
|
cy.get('#swap-currency-output .token-amount-input').should('not.equal', '')
|
||||||
cy.get('#show-advanced').click()
|
|
||||||
cy.get('#swap-button').click()
|
cy.get('#swap-button').click()
|
||||||
cy.get('#confirm-swap-or-send').should('contain', 'Confirm Swap')
|
cy.get('#confirm-swap-or-send').should('contain', 'Confirm Swap')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -80,7 +80,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
"build": "cross-env REACT_APP_GIT_COMMIT_HASH=$(git show -s --format=%H) react-scripts build",
|
"build": "react-scripts build",
|
||||||
"ipfs-build": "cross-env PUBLIC_URL=\".\" react-scripts build",
|
"ipfs-build": "cross-env PUBLIC_URL=\".\" react-scripts build",
|
||||||
"test": "react-scripts test --env=jsdom",
|
"test": "react-scripts test --env=jsdom",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
|
|||||||
@@ -77,9 +77,7 @@ const MenuItem = styled(ExternalLink)`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const CODE_LINK = !!process.env.REACT_APP_GIT_COMMIT_HASH
|
const CODE_LINK = 'https://github.com/Uniswap/uniswap-frontend'
|
||||||
? `https://github.com/Uniswap/uniswap-frontend/tree/${process.env.REACT_APP_GIT_COMMIT_HASH}`
|
|
||||||
: 'https://github.com/Uniswap/uniswap-frontend'
|
|
||||||
|
|
||||||
export default function Menu() {
|
export default function Menu() {
|
||||||
const node = useRef<HTMLDivElement>()
|
const node = useRef<HTMLDivElement>()
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const StyledDialogOverlay = styled(({ mobile, ...rest }) => <AnimatedDialogOverl
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
${({ mobile }) =>
|
${({ mobile }) =>
|
||||||
mobile &&
|
mobile &&
|
||||||
@@ -69,12 +70,10 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...r
|
|||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||||
width: 65vw;
|
width: 65vw;
|
||||||
max-height: 65vh;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
`}
|
`}
|
||||||
${({ theme, mobile }) => theme.mediaWidth.upToSmall`
|
${({ theme, mobile }) => theme.mediaWidth.upToSmall`
|
||||||
width: 85vw;
|
width: 85vw;
|
||||||
max-height: 66vh;
|
|
||||||
${mobile &&
|
${mobile &&
|
||||||
css`
|
css`
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
@@ -86,14 +85,6 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...r
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const HiddenCloseButton = styled.button`
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border: none;
|
|
||||||
`
|
|
||||||
|
|
||||||
interface ModalProps {
|
interface ModalProps {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
onDismiss: () => void
|
onDismiss: () => void
|
||||||
@@ -118,7 +109,7 @@ export default function Modal({
|
|||||||
leave: { opacity: 0 }
|
leave: { opacity: 0 }
|
||||||
})
|
})
|
||||||
|
|
||||||
const [{ xy }, set] = useSpring(() => ({ xy: [0, 0] }))
|
const [{ y }, set] = useSpring(() => ({ y: 0, config: { mass: 1, tension: 210, friction: 20 } }))
|
||||||
const bind = useGesture({
|
const bind = useGesture({
|
||||||
onDrag: state => {
|
onDrag: state => {
|
||||||
let velocity = state.velocity
|
let velocity = state.velocity
|
||||||
@@ -129,8 +120,7 @@ export default function Modal({
|
|||||||
velocity = 8
|
velocity = 8
|
||||||
}
|
}
|
||||||
set({
|
set({
|
||||||
xy: state.down ? state.movement : [0, 0],
|
y: state.down ? state.movement[1] : 0
|
||||||
config: { mass: 1, tension: 210, friction: 20 }
|
|
||||||
})
|
})
|
||||||
if (velocity > 3 && state.direction[1] > 0) {
|
if (velocity > 3 && state.direction[1] > 0) {
|
||||||
onDismiss()
|
onDismiss()
|
||||||
@@ -151,6 +141,8 @@ export default function Modal({
|
|||||||
initialFocusRef={initialFocusRef}
|
initialFocusRef={initialFocusRef}
|
||||||
mobile={true}
|
mobile={true}
|
||||||
>
|
>
|
||||||
|
{/* prevents the automatic focusing of inputs on mobile by the reach dialog */}
|
||||||
|
{initialFocusRef ? null : <div tabIndex={1} />}
|
||||||
<Spring // animation for entrance and exit
|
<Spring // animation for entrance and exit
|
||||||
from={{
|
from={{
|
||||||
transform: isOpen ? 'translateY(200px)' : 'translateY(100px)'
|
transform: isOpen ? 'translateY(200px)' : 'translateY(100px)'
|
||||||
@@ -163,18 +155,17 @@ export default function Modal({
|
|||||||
<animated.div
|
<animated.div
|
||||||
{...bind()}
|
{...bind()}
|
||||||
style={{
|
style={{
|
||||||
transform: (xy as any).interpolate((x, y) => `translate3d(${0}px,${y > 0 ? y : 0}px,0)`)
|
transform: y.interpolate(y => `translateY(${y > 0 ? y : 0}px)`)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<StyledDialogContent
|
<StyledDialogContent
|
||||||
ariaLabel="test"
|
aria-label="dialog content"
|
||||||
style={props}
|
style={props}
|
||||||
hidden={true}
|
hidden={true}
|
||||||
minHeight={minHeight}
|
minHeight={minHeight}
|
||||||
maxHeight={maxHeight}
|
maxHeight={maxHeight}
|
||||||
mobile={isMobile ?? undefined}
|
mobile={isMobile}
|
||||||
>
|
>
|
||||||
<HiddenCloseButton onClick={onDismiss} />
|
|
||||||
{children}
|
{children}
|
||||||
</StyledDialogContent>
|
</StyledDialogContent>
|
||||||
</animated.div>
|
</animated.div>
|
||||||
@@ -192,8 +183,13 @@ export default function Modal({
|
|||||||
({ item, key, props }) =>
|
({ item, key, props }) =>
|
||||||
item && (
|
item && (
|
||||||
<StyledDialogOverlay key={key} style={props} onDismiss={onDismiss} initialFocusRef={initialFocusRef}>
|
<StyledDialogOverlay key={key} style={props} onDismiss={onDismiss} initialFocusRef={initialFocusRef}>
|
||||||
<StyledDialogContent hidden={true} minHeight={minHeight} maxHeight={maxHeight} isOpen={isOpen}>
|
<StyledDialogContent
|
||||||
<HiddenCloseButton onClick={onDismiss} />
|
aria-label="dialog content"
|
||||||
|
hidden={true}
|
||||||
|
minHeight={minHeight}
|
||||||
|
maxHeight={maxHeight}
|
||||||
|
isOpen={isOpen}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</StyledDialogContent>
|
</StyledDialogContent>
|
||||||
</StyledDialogOverlay>
|
</StyledDialogOverlay>
|
||||||
|
|||||||
@@ -54,17 +54,23 @@ function PairSearchModal({ history, isOpen, onDismiss }: PairSearchModalProps) {
|
|||||||
setSearchQuery(checksummedInput || input)
|
setSearchQuery(checksummedInput || input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const filteredPairs = useMemo(() => {
|
||||||
|
return filterPairs(allPairs, searchQuery)
|
||||||
|
}, [allPairs, searchQuery])
|
||||||
|
|
||||||
const sortedPairList = useMemo(() => {
|
const sortedPairList = useMemo(() => {
|
||||||
return allPairs.sort((a, b): number => {
|
const query = searchQuery.toLowerCase()
|
||||||
|
const queryMatches = (pair: Pair): boolean =>
|
||||||
|
pair.token0.symbol.toLowerCase() === query || pair.token1.symbol.toLowerCase() === query
|
||||||
|
return filteredPairs.sort((a, b): number => {
|
||||||
|
const [aMatches, bMatches] = [queryMatches(a), queryMatches(b)]
|
||||||
|
if (aMatches && !bMatches) return -1
|
||||||
|
if (bMatches && !aMatches) return 1
|
||||||
const balanceA = allPairBalances[a.liquidityToken.address]
|
const balanceA = allPairBalances[a.liquidityToken.address]
|
||||||
const balanceB = allPairBalances[b.liquidityToken.address]
|
const balanceB = allPairBalances[b.liquidityToken.address]
|
||||||
return pairComparator(a, b, balanceA, balanceB)
|
return pairComparator(a, b, balanceA, balanceB)
|
||||||
})
|
})
|
||||||
}, [allPairs, allPairBalances])
|
}, [searchQuery, filteredPairs, allPairBalances])
|
||||||
|
|
||||||
const filteredPairs = useMemo(() => {
|
|
||||||
return filterPairs(sortedPairList, searchQuery)
|
|
||||||
}, [searchQuery, sortedPairList])
|
|
||||||
|
|
||||||
const selectPair = useCallback(
|
const selectPair = useCallback(
|
||||||
(pair: Pair) => {
|
(pair: Pair) => {
|
||||||
@@ -110,7 +116,7 @@ function PairSearchModal({ history, isOpen, onDismiss }: PairSearchModalProps) {
|
|||||||
</PaddedColumn>
|
</PaddedColumn>
|
||||||
<div style={{ width: '100%', height: '1px', backgroundColor: theme.bg2 }} />
|
<div style={{ width: '100%', height: '1px', backgroundColor: theme.bg2 }} />
|
||||||
<PairList
|
<PairList
|
||||||
pairs={filteredPairs}
|
pairs={sortedPairList}
|
||||||
focusTokenAddress={focusedToken?.address}
|
focusTokenAddress={focusedToken?.address}
|
||||||
onAddLiquidity={selectPair}
|
onAddLiquidity={selectPair}
|
||||||
onSelectPair={selectPair}
|
onSelectPair={selectPair}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Trade, TradeType } from '@uniswap/sdk'
|
import { Trade, TradeType } from '@uniswap/sdk'
|
||||||
import React, { useContext } from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { ChevronUp, ChevronRight } from 'react-feather'
|
import { ChevronRight } from 'react-feather'
|
||||||
import { Text, Flex } from 'rebass'
|
import { Flex } from 'rebass'
|
||||||
import { ThemeContext } from 'styled-components'
|
import { ThemeContext } from 'styled-components'
|
||||||
import { Field } from '../../state/swap/actions'
|
import { Field } from '../../state/swap/actions'
|
||||||
import { CursorPointer, TYPE } from '../../theme'
|
import { TYPE } from '../../theme'
|
||||||
import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown } from '../../utils/prices'
|
import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown } from '../../utils/prices'
|
||||||
import { AutoColumn } from '../Column'
|
import { AutoColumn } from '../Column'
|
||||||
import { SectionBreak } from './styleds'
|
import { SectionBreak } from './styleds'
|
||||||
@@ -67,29 +67,21 @@ function TradeSummary({ trade, allowedSlippage }: { trade: Trade; allowedSlippag
|
|||||||
|
|
||||||
export interface AdvancedSwapDetailsProps {
|
export interface AdvancedSwapDetailsProps {
|
||||||
trade?: Trade
|
trade?: Trade
|
||||||
onDismiss: () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AdvancedSwapDetails({ trade, onDismiss }: AdvancedSwapDetailsProps) {
|
export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) {
|
||||||
const theme = useContext(ThemeContext)
|
const theme = useContext(ThemeContext)
|
||||||
|
|
||||||
const [allowedSlippage] = useUserSlippageTolerance()
|
const [allowedSlippage] = useUserSlippageTolerance()
|
||||||
|
|
||||||
|
const showRoute = trade?.route?.path?.length > 2
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AutoColumn gap="md">
|
<AutoColumn gap="md">
|
||||||
<CursorPointer>
|
|
||||||
<RowBetween onClick={onDismiss} padding={'8px 20px'}>
|
|
||||||
<Text fontSize={16} color={theme.text2} fontWeight={500} style={{ userSelect: 'none' }}>
|
|
||||||
Hide Advanced
|
|
||||||
</Text>
|
|
||||||
<ChevronUp color={theme.text2} />
|
|
||||||
</RowBetween>
|
|
||||||
</CursorPointer>
|
|
||||||
<SectionBreak />
|
|
||||||
{trade && <TradeSummary trade={trade} allowedSlippage={allowedSlippage} />}
|
{trade && <TradeSummary trade={trade} allowedSlippage={allowedSlippage} />}
|
||||||
{trade?.route?.path?.length > 2 && <SectionBreak />}
|
{showRoute && <SectionBreak />}
|
||||||
{trade?.route?.path?.length > 2 && (
|
{showRoute && (
|
||||||
<AutoColumn style={{ padding: '0 20px' }}>
|
<AutoColumn style={{ padding: '0 24px' }}>
|
||||||
<RowFixed>
|
<RowFixed>
|
||||||
<TYPE.black fontSize={14} fontWeight={400} color={theme.text2}>
|
<TYPE.black fontSize={14} fontWeight={400} color={theme.text2}>
|
||||||
Route
|
Route
|
||||||
|
|||||||
@@ -1,35 +1,30 @@
|
|||||||
import React, { useContext } from 'react'
|
import React from 'react'
|
||||||
import { ChevronDown } from 'react-feather'
|
import styled from 'styled-components'
|
||||||
import { Text } from 'rebass'
|
import useLast from '../../hooks/useLast'
|
||||||
import { ThemeContext } from 'styled-components'
|
|
||||||
import { CursorPointer } from '../../theme'
|
|
||||||
import { RowBetween } from '../Row'
|
|
||||||
import { AdvancedSwapDetails, AdvancedSwapDetailsProps } from './AdvancedSwapDetails'
|
import { AdvancedSwapDetails, AdvancedSwapDetailsProps } from './AdvancedSwapDetails'
|
||||||
import { AdvancedDropdown } from './styleds'
|
|
||||||
|
|
||||||
export default function AdvancedSwapDetailsDropdown({
|
const AdvancedDetailsFooter = styled.div<{ show: boolean }>`
|
||||||
showAdvanced,
|
padding-top: calc(16px + 2rem);
|
||||||
setShowAdvanced,
|
padding-bottom: 20px;
|
||||||
...rest
|
margin-top: -2rem;
|
||||||
}: Omit<AdvancedSwapDetailsProps, 'onDismiss'> & {
|
width: 100%;
|
||||||
showAdvanced: boolean
|
max-width: 400px;
|
||||||
setShowAdvanced: (showAdvanced: boolean) => void
|
border-bottom-left-radius: 20px;
|
||||||
}) {
|
border-bottom-right-radius: 20px;
|
||||||
const theme = useContext(ThemeContext)
|
color: ${({ theme }) => theme.text2};
|
||||||
|
background-color: ${({ theme }) => theme.advancedBG};
|
||||||
|
z-index: -1;
|
||||||
|
|
||||||
|
transform: ${({ show }) => (show ? 'translateY(0%)' : 'translateY(-100%)')};
|
||||||
|
transition: transform 300ms ease-in-out;
|
||||||
|
`
|
||||||
|
|
||||||
|
export default function AdvancedSwapDetailsDropdown({ trade, ...rest }: AdvancedSwapDetailsProps) {
|
||||||
|
const lastTrade = useLast(trade)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdvancedDropdown>
|
<AdvancedDetailsFooter show={Boolean(trade)}>
|
||||||
{showAdvanced ? (
|
<AdvancedSwapDetails {...rest} trade={lastTrade} />
|
||||||
<AdvancedSwapDetails {...rest} onDismiss={() => setShowAdvanced(false)} />
|
</AdvancedDetailsFooter>
|
||||||
) : (
|
|
||||||
<CursorPointer>
|
|
||||||
<RowBetween onClick={() => setShowAdvanced(true)} padding={'8px 20px'} id="show-advanced">
|
|
||||||
<Text fontSize={16} fontWeight={500} style={{ userSelect: 'none' }}>
|
|
||||||
Show Advanced
|
|
||||||
</Text>
|
|
||||||
<ChevronDown color={theme.text2} />
|
|
||||||
</RowBetween>
|
|
||||||
</CursorPointer>
|
|
||||||
)}
|
|
||||||
</AdvancedDropdown>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Trade } from '@uniswap/sdk'
|
import { Price, Token } from '@uniswap/sdk'
|
||||||
import { useContext } from 'react'
|
import { useContext } from 'react'
|
||||||
import { Repeat } from 'react-feather'
|
import { Repeat } from 'react-feather'
|
||||||
import { Text } from 'rebass'
|
import { Text } from 'rebass'
|
||||||
@@ -7,20 +7,19 @@ import { ThemeContext } from 'styled-components'
|
|||||||
import { StyledBalanceMaxMini } from './styleds'
|
import { StyledBalanceMaxMini } from './styleds'
|
||||||
|
|
||||||
interface TradePriceProps {
|
interface TradePriceProps {
|
||||||
trade?: Trade
|
price?: Price
|
||||||
|
inputToken?: Token
|
||||||
|
outputToken?: Token
|
||||||
showInverted: boolean
|
showInverted: boolean
|
||||||
setShowInverted: (showInverted: boolean) => void
|
setShowInverted: (showInverted: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TradePrice({ trade, showInverted, setShowInverted }: TradePriceProps) {
|
export default function TradePrice({ price, inputToken, outputToken, showInverted, setShowInverted }: TradePriceProps) {
|
||||||
const theme = useContext(ThemeContext)
|
const theme = useContext(ThemeContext)
|
||||||
const inputToken = trade?.inputAmount?.token
|
|
||||||
const outputToken = trade?.outputAmount?.token
|
|
||||||
|
|
||||||
const price = showInverted
|
const formattedPrice = showInverted ? price?.toSignificant(6) : price?.invert()?.toSignificant(6)
|
||||||
? trade?.executionPrice?.toSignificant(6)
|
|
||||||
: trade?.executionPrice?.invert()?.toSignificant(6)
|
|
||||||
|
|
||||||
|
const show = Boolean(inputToken && outputToken)
|
||||||
const label = showInverted
|
const label = showInverted
|
||||||
? `${outputToken?.symbol} per ${inputToken?.symbol}`
|
? `${outputToken?.symbol} per ${inputToken?.symbol}`
|
||||||
: `${inputToken?.symbol} per ${outputToken?.symbol}`
|
: `${inputToken?.symbol} per ${outputToken?.symbol}`
|
||||||
@@ -32,10 +31,16 @@ export default function TradePrice({ trade, showInverted, setShowInverted }: Tra
|
|||||||
color={theme.text2}
|
color={theme.text2}
|
||||||
style={{ justifyContent: 'center', alignItems: 'center', display: 'flex' }}
|
style={{ justifyContent: 'center', alignItems: 'center', display: 'flex' }}
|
||||||
>
|
>
|
||||||
{price && `${price} ${label}`}
|
{show ? (
|
||||||
<StyledBalanceMaxMini onClick={() => setShowInverted(!showInverted)}>
|
<>
|
||||||
<Repeat size={14} />
|
{formattedPrice ?? '-'} {label}
|
||||||
</StyledBalanceMaxMini>
|
<StyledBalanceMaxMini onClick={() => setShowInverted(!showInverted)}>
|
||||||
|
<Repeat size={14} />
|
||||||
|
</StyledBalanceMaxMini>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
'-'
|
||||||
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,19 +21,6 @@ export const ArrowWrapper = styled.div`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const AdvancedDropdown = styled.div`
|
|
||||||
padding-top: calc(10px + 2rem);
|
|
||||||
padding-bottom: 10px;
|
|
||||||
margin-top: -2rem;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 400px;
|
|
||||||
border-bottom-left-radius: 20px;
|
|
||||||
border-bottom-right-radius: 20px;
|
|
||||||
color: ${({ theme }) => theme.text2};
|
|
||||||
background-color: ${({ theme }) => theme.advancedBG};
|
|
||||||
z-index: -1;
|
|
||||||
`
|
|
||||||
|
|
||||||
export const SectionBreak = styled.div`
|
export const SectionBreak = styled.div`
|
||||||
height: 1px;
|
height: 1px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -71,7 +58,7 @@ export const StyledNumerical = styled(NumericalInput)`
|
|||||||
color: ${({ theme }) => theme.text4};
|
color: ${({ theme }) => theme.text4};
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
export const StyledBalanceMaxMini = styled.button<{ active?: boolean }>`
|
export const StyledBalanceMaxMini = styled.button`
|
||||||
height: 22px;
|
height: 22px;
|
||||||
width: 22px;
|
width: 22px;
|
||||||
background-color: ${({ theme }) => theme.bg2};
|
background-color: ${({ theme }) => theme.bg2};
|
||||||
|
|||||||
@@ -151,13 +151,12 @@ export const ONE_BIPS = new Percent(JSBI.BigInt(1), JSBI.BigInt(10000))
|
|||||||
export const BIPS_BASE = JSBI.BigInt(10000)
|
export const BIPS_BASE = JSBI.BigInt(10000)
|
||||||
// used for warning states
|
// used for warning states
|
||||||
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(300), BIPS_BASE) // 3%
|
||||||
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(500), BIPS_BASE) // 5%
|
||||||
// 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(1000), BIPS_BASE) // 10%
|
||||||
|
// for non expert mode disable swaps above this
|
||||||
|
export const BLOCKED_PRICE_IMPACT_NON_EXPERT: Percent = new Percent(JSBI.BigInt(1500), BIPS_BASE) // 15%
|
||||||
|
|
||||||
// used to ensure the user doesn't send so much ETH so they end up with <.01
|
// used to ensure the user doesn't send so much ETH so they end up with <.01
|
||||||
export const MIN_ETH: JSBI = JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(16)) // .01 ETH
|
export const MIN_ETH: JSBI = JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(16)) // .01 ETH
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export default [
|
|||||||
new Token(ChainId.MAINNET, '0x39AA39c021dfbaE8faC545936693aC917d5E7563', 8, 'cUSDC', 'Compound USD Coin'),
|
new Token(ChainId.MAINNET, '0x39AA39c021dfbaE8faC545936693aC917d5E7563', 8, 'cUSDC', 'Compound USD Coin'),
|
||||||
new Token(ChainId.MAINNET, '0xaaAEBE6Fe48E54f431b0C390CfaF0b017d09D42d', 4, 'CEL', 'Celsius'),
|
new Token(ChainId.MAINNET, '0xaaAEBE6Fe48E54f431b0C390CfaF0b017d09D42d', 4, 'CEL', 'Celsius'),
|
||||||
new Token(ChainId.MAINNET, '0x06AF07097C9Eeb7fD685c692751D5C66dB49c215', 18, 'CHAI', 'Chai'),
|
new Token(ChainId.MAINNET, '0x06AF07097C9Eeb7fD685c692751D5C66dB49c215', 18, 'CHAI', 'Chai'),
|
||||||
|
new Token(ChainId.MAINNET, '0xc00e94Cb662C3520282E6f5717214004A7f26888', 18, 'COMP', 'Compound'),
|
||||||
new Token(ChainId.MAINNET, '0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359', 18, 'SAI', 'Dai Stablecoin v1.0 (SAI)'),
|
new Token(ChainId.MAINNET, '0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359', 18, 'SAI', 'Dai Stablecoin v1.0 (SAI)'),
|
||||||
new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin'),
|
new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin'),
|
||||||
new Token(ChainId.MAINNET, '0x0Cf0Ee63788A0849fE5297F3407f701E122cC023', 18, 'DATA', 'Streamr DATAcoin'),
|
new Token(ChainId.MAINNET, '0x0Cf0Ee63788A0849fE5297F3407f701E122cC023', 18, 'DATA', 'Streamr DATAcoin'),
|
||||||
@@ -64,6 +65,7 @@ export default [
|
|||||||
new Token(ChainId.MAINNET, '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2', 18, 'MKR', 'Maker'),
|
new Token(ChainId.MAINNET, '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2', 18, 'MKR', 'Maker'),
|
||||||
new Token(ChainId.MAINNET, '0xec67005c4E498Ec7f55E092bd1d35cbC47C91892', 18, 'MLN', 'Melon Token'),
|
new Token(ChainId.MAINNET, '0xec67005c4E498Ec7f55E092bd1d35cbC47C91892', 18, 'MLN', 'Melon Token'),
|
||||||
new Token(ChainId.MAINNET, '0x957c30aB0426e0C93CD8241E2c60392d08c6aC8e', 0, 'MOD', 'Modum Token'),
|
new Token(ChainId.MAINNET, '0x957c30aB0426e0C93CD8241E2c60392d08c6aC8e', 0, 'MOD', 'Modum Token'),
|
||||||
|
new Token(ChainId.MAINNET, '0xe2f2a5C287993345a840Db3B0845fbC70f5935a5', 18, 'mUSD', 'mStable USD'),
|
||||||
new Token(ChainId.MAINNET, '0xB62132e35a6c13ee1EE0f84dC5d40bad8d815206', 18, 'NEXO', 'Nexo'),
|
new Token(ChainId.MAINNET, '0xB62132e35a6c13ee1EE0f84dC5d40bad8d815206', 18, 'NEXO', 'Nexo'),
|
||||||
new Token(ChainId.MAINNET, '0x1776e1F26f98b1A5dF9cD347953a26dd3Cb46671', 18, 'NMR', 'Numeraire'),
|
new Token(ChainId.MAINNET, '0x1776e1F26f98b1A5dF9cD347953a26dd3Cb46671', 18, 'NMR', 'Numeraire'),
|
||||||
new Token(ChainId.MAINNET, '0x985dd3D42De1e256d09e1c10F112bCCB8015AD41', 18, 'OCEAN', 'OceanToken'),
|
new Token(ChainId.MAINNET, '0x985dd3D42De1e256d09e1c10F112bCCB8015AD41', 18, 'OCEAN', 'OceanToken'),
|
||||||
@@ -94,6 +96,7 @@ export default [
|
|||||||
new Token(ChainId.MAINNET, '0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F', 18, 'SNX', 'Synthetix Network Token'),
|
new Token(ChainId.MAINNET, '0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F', 18, 'SNX', 'Synthetix Network Token'),
|
||||||
new Token(ChainId.MAINNET, '0x23B608675a2B2fB1890d3ABBd85c5775c51691d5', 18, 'SOCKS', 'Unisocks Edition 0'),
|
new Token(ChainId.MAINNET, '0x23B608675a2B2fB1890d3ABBd85c5775c51691d5', 18, 'SOCKS', 'Unisocks Edition 0'),
|
||||||
new Token(ChainId.MAINNET, '0x42d6622deCe394b54999Fbd73D108123806f6a18', 18, 'SPANK', 'SPANK'),
|
new Token(ChainId.MAINNET, '0x42d6622deCe394b54999Fbd73D108123806f6a18', 18, 'SPANK', 'SPANK'),
|
||||||
|
new Token(ChainId.MAINNET, '0x0Ae055097C6d159879521C384F1D2123D1f195e6', 18, 'STAKE', 'STAKE'),
|
||||||
new Token(ChainId.MAINNET, '0xB64ef51C888972c908CFacf59B47C1AfBC0Ab8aC', 8, 'STORJ', 'StorjToken'),
|
new Token(ChainId.MAINNET, '0xB64ef51C888972c908CFacf59B47C1AfBC0Ab8aC', 8, 'STORJ', 'StorjToken'),
|
||||||
new Token(ChainId.MAINNET, '0x57Ab1ec28D129707052df4dF418D58a2D46d5f51', 18, 'sUSD', 'Synth sUSD'),
|
new Token(ChainId.MAINNET, '0x57Ab1ec28D129707052df4dF418D58a2D46d5f51', 18, 'sUSD', 'Synth sUSD'),
|
||||||
new Token(ChainId.MAINNET, '0x261EfCdD24CeA98652B9700800a13DfBca4103fF', 18, 'sXAU', 'Synth sXAU'),
|
new Token(ChainId.MAINNET, '0x261EfCdD24CeA98652B9700800a13DfBca4103fF', 18, 'sXAU', 'Synth sXAU'),
|
||||||
|
|||||||
13
src/hooks/useLast.ts
Normal file
13
src/hooks/useLast.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the last truthy value of type T
|
||||||
|
* @param value changing value
|
||||||
|
*/
|
||||||
|
export default function useLast<T>(value: T | undefined | null): T | null | undefined {
|
||||||
|
const [last, setLast] = useState<T | null | undefined>(value)
|
||||||
|
useEffect(() => {
|
||||||
|
setLast(last => value ?? last)
|
||||||
|
}, [value])
|
||||||
|
return last
|
||||||
|
}
|
||||||
@@ -270,8 +270,8 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
|
|||||||
|
|
||||||
const PriceBar = () => {
|
const PriceBar = () => {
|
||||||
return (
|
return (
|
||||||
<AutoColumn gap="md" justify="space-between">
|
<AutoColumn gap="md">
|
||||||
<AutoRow justify="space-between">
|
<AutoRow justify="space-around" gap="4px">
|
||||||
<AutoColumn justify="center">
|
<AutoColumn justify="center">
|
||||||
<TYPE.black>{price?.toSignificant(6) ?? '0'}</TYPE.black>
|
<TYPE.black>{price?.toSignificant(6) ?? '0'}</TYPE.black>
|
||||||
<Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}>
|
<Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}>
|
||||||
|
|||||||
@@ -10,11 +10,9 @@ import Card, { BlueCard, GreyCard } from '../../components/Card'
|
|||||||
import { AutoColumn, ColumnCenter } from '../../components/Column'
|
import { AutoColumn, ColumnCenter } from '../../components/Column'
|
||||||
import ConfirmationModal from '../../components/ConfirmationModal'
|
import ConfirmationModal from '../../components/ConfirmationModal'
|
||||||
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
|
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
|
||||||
import QuestionHelper from '../../components/QuestionHelper'
|
import { AutoRow, RowBetween } from '../../components/Row'
|
||||||
import { AutoRow, RowBetween, RowFixed } from '../../components/Row'
|
|
||||||
import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetailsDropdown'
|
import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetailsDropdown'
|
||||||
import confirmPriceImpactWithoutFee from '../../components/swap/confirmPriceImpactWithoutFee'
|
import confirmPriceImpactWithoutFee from '../../components/swap/confirmPriceImpactWithoutFee'
|
||||||
import FormattedPriceImpact from '../../components/swap/FormattedPriceImpact'
|
|
||||||
import SwapModalFooter from '../../components/swap/SwapModalFooter'
|
import SwapModalFooter from '../../components/swap/SwapModalFooter'
|
||||||
import { ArrowWrapper, BottomGrouping, Dots, InputGroup, StyledNumerical, Wrapper } from '../../components/swap/styleds'
|
import { ArrowWrapper, BottomGrouping, Dots, InputGroup, StyledNumerical, Wrapper } from '../../components/swap/styleds'
|
||||||
import TradePrice from '../../components/swap/TradePrice'
|
import TradePrice from '../../components/swap/TradePrice'
|
||||||
@@ -98,7 +96,6 @@ export default function Send() {
|
|||||||
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
|
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
|
||||||
|
|
||||||
// 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) // 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>('')
|
||||||
@@ -476,14 +473,20 @@ export default function Send() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</AutoColumn>
|
</AutoColumn>
|
||||||
{!noRoute && tokens[Field.OUTPUT] && tokens[Field.INPUT] && (
|
{sendingWithSwap && (
|
||||||
<Card padding={'.25rem .75rem 0 .75rem'} borderRadius={'20px'}>
|
<Card padding={'.25rem .75rem 0 .75rem'} borderRadius={'20px'}>
|
||||||
<AutoColumn gap="4px">
|
<AutoColumn gap="4px">
|
||||||
<RowBetween align="center">
|
<RowBetween align="center">
|
||||||
<Text fontWeight={500} fontSize={14} color={theme.text2}>
|
<Text fontWeight={500} fontSize={14} color={theme.text2}>
|
||||||
Price
|
Price
|
||||||
</Text>
|
</Text>
|
||||||
<TradePrice showInverted={showInverted} setShowInverted={setShowInverted} trade={bestTrade} />
|
<TradePrice
|
||||||
|
inputToken={tokens[Field.INPUT]}
|
||||||
|
outputToken={tokens[Field.OUTPUT]}
|
||||||
|
price={bestTrade?.executionPrice}
|
||||||
|
showInverted={showInverted}
|
||||||
|
setShowInverted={setShowInverted}
|
||||||
|
/>
|
||||||
</RowBetween>
|
</RowBetween>
|
||||||
|
|
||||||
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
|
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
|
||||||
@@ -500,20 +503,6 @@ export default function Send() {
|
|||||||
</ClickableText>
|
</ClickableText>
|
||||||
</RowBetween>
|
</RowBetween>
|
||||||
)}
|
)}
|
||||||
{bestTrade && severity > 1 && (
|
|
||||||
<RowBetween>
|
|
||||||
<TYPE.main
|
|
||||||
style={{ justifyContent: 'center', alignItems: 'center', display: 'flex' }}
|
|
||||||
fontSize={14}
|
|
||||||
>
|
|
||||||
Price Impact
|
|
||||||
</TYPE.main>
|
|
||||||
<RowFixed>
|
|
||||||
<FormattedPriceImpact priceImpact={priceImpactWithoutFee} />
|
|
||||||
<QuestionHelper text="The difference between the market price and estimated price due to trade size." />
|
|
||||||
</RowFixed>
|
|
||||||
</RowBetween>
|
|
||||||
)}
|
|
||||||
</AutoColumn>
|
</AutoColumn>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
@@ -588,9 +577,7 @@ export default function Send() {
|
|||||||
</Wrapper>
|
</Wrapper>
|
||||||
</AppBody>
|
</AppBody>
|
||||||
|
|
||||||
{bestTrade && (
|
<AdvancedSwapDetailsDropdown trade={bestTrade} />
|
||||||
<AdvancedSwapDetailsDropdown trade={bestTrade} showAdvanced={showAdvanced} setShowAdvanced={setShowAdvanced} />
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,9 @@ import Card, { GreyCard } from '../../components/Card'
|
|||||||
import { AutoColumn } from '../../components/Column'
|
import { AutoColumn } from '../../components/Column'
|
||||||
import ConfirmationModal from '../../components/ConfirmationModal'
|
import ConfirmationModal from '../../components/ConfirmationModal'
|
||||||
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
|
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
|
||||||
import QuestionHelper from '../../components/QuestionHelper'
|
import { RowBetween } from '../../components/Row'
|
||||||
import { RowBetween, RowFixed } from '../../components/Row'
|
|
||||||
import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetailsDropdown'
|
import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetailsDropdown'
|
||||||
import confirmPriceImpactWithoutFee from '../../components/swap/confirmPriceImpactWithoutFee'
|
import confirmPriceImpactWithoutFee from '../../components/swap/confirmPriceImpactWithoutFee'
|
||||||
import FormattedPriceImpact from '../../components/swap/FormattedPriceImpact'
|
|
||||||
import { ArrowWrapper, BottomGrouping, Dots, Wrapper } from '../../components/swap/styleds'
|
import { ArrowWrapper, BottomGrouping, Dots, Wrapper } from '../../components/swap/styleds'
|
||||||
import SwapModalFooter from '../../components/swap/SwapModalFooter'
|
import SwapModalFooter from '../../components/swap/SwapModalFooter'
|
||||||
import SwapModalHeader from '../../components/swap/SwapModalHeader'
|
import SwapModalHeader from '../../components/swap/SwapModalHeader'
|
||||||
@@ -84,7 +82,6 @@ export default function Swap() {
|
|||||||
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
|
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
|
||||||
|
|
||||||
// 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) // 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>('')
|
||||||
@@ -284,44 +281,33 @@ export default function Swap() {
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|
||||||
{!noRoute && tokens[Field.OUTPUT] && tokens[Field.INPUT] && (
|
<Card padding={'.25rem .75rem 0 .75rem'} borderRadius={'20px'}>
|
||||||
<Card padding={'.25rem .75rem 0 .75rem'} borderRadius={'20px'}>
|
<AutoColumn gap="4px">
|
||||||
<AutoColumn gap="4px">
|
<RowBetween align="center">
|
||||||
|
<Text fontWeight={500} fontSize={14} color={theme.text2}>
|
||||||
|
Price
|
||||||
|
</Text>
|
||||||
|
<TradePrice
|
||||||
|
inputToken={tokens[Field.INPUT]}
|
||||||
|
outputToken={tokens[Field.OUTPUT]}
|
||||||
|
price={bestTrade?.executionPrice}
|
||||||
|
showInverted={showInverted}
|
||||||
|
setShowInverted={setShowInverted}
|
||||||
|
/>
|
||||||
|
</RowBetween>
|
||||||
|
|
||||||
|
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
|
||||||
<RowBetween align="center">
|
<RowBetween align="center">
|
||||||
<Text fontWeight={500} fontSize={14} color={theme.text2}>
|
<ClickableText fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
|
||||||
Price
|
Slippage Tolerance
|
||||||
</Text>
|
</ClickableText>
|
||||||
<TradePrice trade={bestTrade} showInverted={showInverted} setShowInverted={setShowInverted} />
|
<ClickableText fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
|
||||||
|
{allowedSlippage ? allowedSlippage / 100 : '-'}%
|
||||||
|
</ClickableText>
|
||||||
</RowBetween>
|
</RowBetween>
|
||||||
|
)}
|
||||||
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
|
</AutoColumn>
|
||||||
<RowBetween align="center">
|
</Card>
|
||||||
<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
|
|
||||||
style={{ justifyContent: 'center', alignItems: 'center', display: 'flex' }}
|
|
||||||
fontSize={14}
|
|
||||||
>
|
|
||||||
Price Impact
|
|
||||||
</TYPE.main>
|
|
||||||
<RowFixed>
|
|
||||||
<FormattedPriceImpact priceImpact={priceImpactWithoutFee} />
|
|
||||||
<QuestionHelper text="The difference between the market price and estimated price due to trade size." />
|
|
||||||
</RowFixed>
|
|
||||||
</RowBetween>
|
|
||||||
)}
|
|
||||||
</AutoColumn>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
</AutoColumn>
|
</AutoColumn>
|
||||||
<BottomGrouping>
|
<BottomGrouping>
|
||||||
{!account ? (
|
{!account ? (
|
||||||
@@ -385,9 +371,7 @@ export default function Swap() {
|
|||||||
</Wrapper>
|
</Wrapper>
|
||||||
</AppBody>
|
</AppBody>
|
||||||
|
|
||||||
{bestTrade && (
|
<AdvancedSwapDetailsDropdown trade={bestTrade} />
|
||||||
<AdvancedSwapDetailsDropdown trade={bestTrade} showAdvanced={showAdvanced} setShowAdvanced={setShowAdvanced} />
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
29
src/state/user/reducer.test.ts
Normal file
29
src/state/user/reducer.test.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { createStore, Store } from 'redux'
|
||||||
|
import { DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE } from '../../constants'
|
||||||
|
import { updateVersion } from './actions'
|
||||||
|
import reducer, { initialState, UserState } from './reducer'
|
||||||
|
|
||||||
|
describe('swap reducer', () => {
|
||||||
|
let store: Store<UserState>
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
store = createStore(reducer, initialState)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('updateVersion', () => {
|
||||||
|
it('has no timestamp originally', () => {
|
||||||
|
expect(store.getState().lastUpdateVersionTimestamp).toBeUndefined()
|
||||||
|
})
|
||||||
|
it('sets the lastUpdateVersionTimestamp', () => {
|
||||||
|
const time = new Date().getTime()
|
||||||
|
store.dispatch(updateVersion())
|
||||||
|
expect(store.getState().lastUpdateVersionTimestamp).toBeGreaterThanOrEqual(time)
|
||||||
|
})
|
||||||
|
it('sets allowed slippage and deadline', () => {
|
||||||
|
store = createStore(reducer, { ...initialState, userDeadline: undefined, userSlippageTolerance: undefined })
|
||||||
|
store.dispatch(updateVersion())
|
||||||
|
expect(store.getState().userDeadline).toEqual(DEFAULT_DEADLINE_FROM_NOW)
|
||||||
|
expect(store.getState().userSlippageTolerance).toEqual(INITIAL_ALLOWED_SLIPPAGE)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { INITIAL_ALLOWED_SLIPPAGE, DEFAULT_DEADLINE_FROM_NOW } from './../../constants/index'
|
import { INITIAL_ALLOWED_SLIPPAGE, DEFAULT_DEADLINE_FROM_NOW } from '../../constants'
|
||||||
import { createReducer } from '@reduxjs/toolkit'
|
import { createReducer } from '@reduxjs/toolkit'
|
||||||
import {
|
import {
|
||||||
addSerializedPair,
|
addSerializedPair,
|
||||||
@@ -18,8 +18,9 @@ import {
|
|||||||
|
|
||||||
const currentTimestamp = () => new Date().getTime()
|
const currentTimestamp = () => new Date().getTime()
|
||||||
|
|
||||||
interface UserState {
|
export interface UserState {
|
||||||
lastVersion: string
|
// the timestamp of the last updateVersion action
|
||||||
|
lastUpdateVersionTimestamp?: number
|
||||||
|
|
||||||
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
|
||||||
@@ -59,8 +60,7 @@ function pairKey(token0Address: string, token1Address: string) {
|
|||||||
return `${token0Address};${token1Address}`
|
return `${token0Address};${token1Address}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: UserState = {
|
export const initialState: UserState = {
|
||||||
lastVersion: '',
|
|
||||||
userDarkMode: null,
|
userDarkMode: null,
|
||||||
matchesDarkMode: false,
|
matchesDarkMode: false,
|
||||||
userExpertMode: false,
|
userExpertMode: false,
|
||||||
@@ -71,25 +71,20 @@ const initialState: UserState = {
|
|||||||
timestamp: currentTimestamp()
|
timestamp: currentTimestamp()
|
||||||
}
|
}
|
||||||
|
|
||||||
const GIT_COMMIT_HASH: string | undefined = process.env.REACT_APP_GIT_COMMIT_HASH
|
|
||||||
|
|
||||||
export default createReducer(initialState, builder =>
|
export default createReducer(initialState, builder =>
|
||||||
builder
|
builder
|
||||||
.addCase(updateVersion, state => {
|
.addCase(updateVersion, state => {
|
||||||
if (GIT_COMMIT_HASH && state.lastVersion !== GIT_COMMIT_HASH) {
|
// slippage isnt being tracked in local storage, reset to default
|
||||||
state.lastVersion = GIT_COMMIT_HASH
|
if (typeof state.userSlippageTolerance !== 'number') {
|
||||||
|
state.userSlippageTolerance = INITIAL_ALLOWED_SLIPPAGE
|
||||||
// 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()
|
|
||||||
|
// deadline isnt being tracked in local storage, reset to default
|
||||||
|
if (typeof state.userDeadline !== 'number') {
|
||||||
|
state.userDeadline = DEFAULT_DEADLINE_FROM_NOW
|
||||||
|
}
|
||||||
|
|
||||||
|
state.lastUpdateVersionTimestamp = currentTimestamp()
|
||||||
})
|
})
|
||||||
.addCase(updateUserDarkMode, (state, action) => {
|
.addCase(updateUserDarkMode, (state, action) => {
|
||||||
state.userDarkMode = action.payload.userDarkMode
|
state.userDarkMode = action.payload.userDarkMode
|
||||||
|
|||||||
Reference in New Issue
Block a user