Compare commits

...

9 Commits

Author SHA1 Message Date
Moody Salem
a5ff3beb92 add a comment for the previous change 2020-06-22 16:12:02 -05:00
Moody Salem
35ccf425f6 improvement(modals): do not focus inputs automatically on mobile 2020-06-22 16:11:32 -05:00
Moody Salem
fe030412cd improvement(pair search modal): show exact symbol match pairs first, filter before sorting 2020-06-22 14:37:52 -05:00
Moody Salem
4d5a43351f fix(trustwallet confirm modal): fix the confirmation modal to not be obscured by the trustwallet bar 2020-06-15 23:09:33 -04:00
Moody Salem
ac1bc3b3a6 cleanup(modal): clean up modal code a bit 2020-06-15 16:46:38 -04:00
Noah Zinsmeister
d1063d50ed add COMP, mUSD, STAKE 2020-06-15 16:24:39 -04:00
Moody Salem
46fc74e90f chore(deploy): trigger a deploy and also trigger deploys on .env.production changes 2020-06-15 10:19:46 -04:00
Moody Salem
2c4f4092d8 improvement(advanced): always show advanced (#890)
* always show advanced

* fix test

* always show the price, less jitter

* show tokens in price field

* fix the dropdown sticking around when switching between swap/send

* lint

* fix ios scrolling the modal body into the viewport bug

* don't use react-spring for simple slide animation

* safer price impact constants
2020-06-15 10:13:12 -04:00
Moody Salem
aac7268dc8 just drop the git commit hash (more trouble than it's worth) 2020-06-15 09:36:39 -04:00
18 changed files with 187 additions and 198 deletions

View File

@@ -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:

View File

@@ -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')
}) })

View File

@@ -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",

View File

@@ -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>()

View File

@@ -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>

View File

@@ -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}

View File

@@ -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

View File

@@ -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>
) )
} }

View File

@@ -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>
) )
} }

View File

@@ -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};

View File

@@ -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

View File

@@ -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
View 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
}

View File

@@ -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}>

View File

@@ -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} />
)}
</> </>
) )
} }

View File

@@ -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} />
)}
</> </>
) )
} }

View 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)
})
})
})

View File

@@ -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