Commit f2f960f6 authored by Noah Zinsmeister's avatar Noah Zinsmeister Committed by GitHub

various improvements (#313)

* add unchecked signer

* remove focus underline from tabs

* update tokens

* remove console log

* remove snx for now

* make slippage warnings more robust

* memo-ize contexts

* improve slippage styling
parent 6579e17f
...@@ -6,7 +6,9 @@ ...@@ -6,7 +6,9 @@
"installMetamask": "Please visit us after installing Metamask on Chrome or Brave.", "installMetamask": "Please visit us after installing Metamask on Chrome or Brave.",
"disconnected": "Disconnected", "disconnected": "Disconnected",
"swap": "Swap", "swap": "Swap",
"swapAnyway": "Swap Anyway",
"send": "Send", "send": "Send",
"sendAnyway": "Send Anyway",
"pool": "Pool", "pool": "Pool",
"betaWarning": "This project is in beta. Use at your own risk.", "betaWarning": "This project is in beta. Use at your own risk.",
"input": "Input", "input": "Input",
...@@ -28,6 +30,7 @@ ...@@ -28,6 +30,7 @@
"transactionDetails": "Transaction Details", "transactionDetails": "Transaction Details",
"hideDetails": "Hide Details", "hideDetails": "Hide Details",
"slippageWarning": "Slippage Warning", "slippageWarning": "Slippage Warning",
"highSlippageWarning": "High Slippage Warning",
"youAreSelling": "You are selling", "youAreSelling": "You are selling",
"orTransFail": "or the transaction will fail.", "orTransFail": "or the transaction will fail.",
"youWillReceive": "You will receive at least", "youWillReceive": "You will receive at least",
......
import React, { useState } from 'react' import React, { useState } from 'react'
import styled from 'styled-components' import styled, { css } from 'styled-components'
import { transparentize } from 'polished'
import { ReactComponent as Dropup } from '../../assets/images/dropup-blue.svg' import { ReactComponent as Dropup } from '../../assets/images/dropup-blue.svg'
import { ReactComponent as Dropdown } from '../../assets/images/dropdown-blue.svg' import { ReactComponent as Dropdown } from '../../assets/images/dropdown-blue.svg'
...@@ -23,10 +24,6 @@ const SummaryWrapperContainer = styled.div` ...@@ -23,10 +24,6 @@ const SummaryWrapperContainer = styled.div`
justify-content: center; justify-content: center;
font-size: 0.75rem; font-size: 0.75rem;
span {
margin-right: 12px;
}
img { img {
height: 0.75rem; height: 0.75rem;
width: 0.75rem; width: 0.75rem;
...@@ -42,13 +39,33 @@ const Details = styled.div` ...@@ -42,13 +39,33 @@ const Details = styled.div`
` `
const ErrorSpan = styled.span` const ErrorSpan = styled.span`
margin-right: 12px;
color: ${({ isError, theme }) => isError && theme.salmonRed}; color: ${({ isError, theme }) => isError && theme.salmonRed};
${({ slippageWarning, highSlippageWarning, theme }) =>
highSlippageWarning
? css`
color: ${theme.salmonRed};
font-weight: 600;
`
: slippageWarning &&
css`
background-color: ${transparentize(0.6, theme.warningYellow)};
font-weight: 600;
padding: 0.25rem;
`}
` `
const WrappedDropup = ({ isError, ...rest }) => <Dropup {...rest} /> const WrappedDropup = ({ isError, ...rest }) => <Dropup {...rest} />
const ColoredDropup = styled(WrappedDropup)` const ColoredDropup = styled(WrappedDropup)`
path { path {
stroke: ${({ isError, theme }) => isError && theme.salmonRed}; stroke: ${({ isError, theme }) => isError && theme.salmonRed};
${({ highSlippageWarning, theme }) =>
highSlippageWarning &&
css`
stroke: ${theme.salmonRed};
`}
} }
` `
...@@ -56,6 +73,12 @@ const WrappedDropdown = ({ isError, ...rest }) => <Dropdown {...rest} /> ...@@ -56,6 +73,12 @@ const WrappedDropdown = ({ isError, ...rest }) => <Dropdown {...rest} />
const ColoredDropdown = styled(WrappedDropdown)` const ColoredDropdown = styled(WrappedDropdown)`
path { path {
stroke: ${({ isError, theme }) => isError && theme.salmonRed}; stroke: ${({ isError, theme }) => isError && theme.salmonRed};
${({ highSlippageWarning, theme }) =>
highSlippageWarning &&
css`
stroke: ${theme.salmonRed};
`}
} }
` `
...@@ -65,7 +88,9 @@ export default function ContextualInfo({ ...@@ -65,7 +88,9 @@ export default function ContextualInfo({
contextualInfo = '', contextualInfo = '',
allowExpand = false, allowExpand = false,
renderTransactionDetails = () => {}, renderTransactionDetails = () => {},
isError = false isError = false,
slippageWarning,
highSlippageWarning
}) { }) {
const [showDetails, setShowDetails] = useState(false) const [showDetails, setShowDetails] = useState(false)
...@@ -75,10 +100,19 @@ export default function ContextualInfo({ ...@@ -75,10 +100,19 @@ export default function ContextualInfo({
<> <>
<SummaryWrapperContainer onClick={() => setShowDetails(s => !s)}> <SummaryWrapperContainer onClick={() => setShowDetails(s => !s)}>
<> <>
<ErrorSpan isError={isError}> <ErrorSpan isError={isError} slippageWarning={slippageWarning} highSlippageWarning={highSlippageWarning}>
{(slippageWarning || highSlippageWarning) && (
<span role="img" aria-label="warning">
⚠️
</span>
)}
{contextualInfo ? contextualInfo : showDetails ? closeDetailsText : openDetailsText} {contextualInfo ? contextualInfo : showDetails ? closeDetailsText : openDetailsText}
</ErrorSpan> </ErrorSpan>
{showDetails ? <ColoredDropup isError={isError} /> : <ColoredDropdown isError={isError} />} {showDetails ? (
<ColoredDropup isError={isError} highSlippageWarning={highSlippageWarning} />
) : (
<ColoredDropdown isError={isError} highSlippageWarning={highSlippageWarning} />
)}
</> </>
</SummaryWrapperContainer> </SummaryWrapperContainer>
{showDetails && <Details>{renderTransactionDetails()}</Details>} {showDetails && <Details>{renderTransactionDetails()}</Details>}
......
...@@ -236,7 +236,7 @@ export default function CurrencyInputPanel({ ...@@ -236,7 +236,7 @@ export default function CurrencyInputPanel({
gasLimit: calculateGasMargin(estimatedGas, GAS_MARGIN) gasLimit: calculateGasMargin(estimatedGas, GAS_MARGIN)
}) })
.then(response => { .then(response => {
addTransaction(response) addTransaction(response, { approval: selectedTokenAddress })
}) })
}} }}
> >
......
...@@ -94,12 +94,8 @@ const StyledNavLink = styled(NavLink).attrs({ ...@@ -94,12 +94,8 @@ const StyledNavLink = styled(NavLink).attrs({
:hover, :hover,
:focus { :focus {
font-weight: 500;
color: ${({ theme }) => darken(0.1, theme.royalBlue)}; color: ${({ theme }) => darken(0.1, theme.royalBlue)};
/* box-shadow: 0 0 0.5px 0.5px ${({ theme }) => darken(0.2, theme.mercuryGray)}; */
}
:focus {
text-decoration: underline;
} }
` `
......
import React, { createContext, useContext, useReducer, useCallback, useEffect } from 'react' import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
import { useWeb3Context } from 'web3-react' import { useWeb3Context } from 'web3-react'
import { safeAccess, isAddress, getTokenAllowance } from '../utils' import { safeAccess, isAddress, getTokenAllowance } from '../utils'
...@@ -46,7 +46,11 @@ export default function Provider({ children }) { ...@@ -46,7 +46,11 @@ export default function Provider({ children }) {
dispatch({ type: UPDATE, payload: { networkId, address, tokenAddress, spenderAddress, value, blockNumber } }) dispatch({ type: UPDATE, payload: { networkId, address, tokenAddress, spenderAddress, value, blockNumber } })
}, []) }, [])
return <AllowancesContext.Provider value={[state, { update }]}>{children}</AllowancesContext.Provider> return (
<AllowancesContext.Provider value={useMemo(() => [state, { update }], [state, update])}>
{children}
</AllowancesContext.Provider>
)
} }
export function useAddressAllowance(address, tokenAddress, spenderAddress) { export function useAddressAllowance(address, tokenAddress, spenderAddress) {
......
import React, { createContext, useContext, useReducer, useCallback, useEffect } from 'react' import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
import { useWeb3Context } from 'web3-react' import { useWeb3Context } from 'web3-react'
import { safeAccess } from '../utils' import { safeAccess } from '../utils'
...@@ -52,7 +52,13 @@ export default function Provider({ children }) { ...@@ -52,7 +52,13 @@ export default function Provider({ children }) {
}, []) }, [])
return ( return (
<ApplicationContext.Provider value={[state, { dismissBetaMessage, updateBlockNumber }]}> <ApplicationContext.Provider
value={useMemo(() => [state, { dismissBetaMessage, updateBlockNumber }], [
state,
dismissBetaMessage,
updateBlockNumber
])}
>
{children} {children}
</ApplicationContext.Provider> </ApplicationContext.Provider>
) )
......
import React, { createContext, useContext, useReducer, useCallback, useEffect } from 'react' import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
import { useWeb3Context } from 'web3-react' import { useWeb3Context } from 'web3-react'
import { safeAccess, isAddress, getEtherBalance, getTokenBalance } from '../utils' import { safeAccess, isAddress, getEtherBalance, getTokenBalance } from '../utils'
...@@ -44,7 +44,11 @@ export default function Provider({ children }) { ...@@ -44,7 +44,11 @@ export default function Provider({ children }) {
dispatch({ type: UPDATE, payload: { networkId, address, tokenAddress, value, blockNumber } }) dispatch({ type: UPDATE, payload: { networkId, address, tokenAddress, value, blockNumber } })
}, []) }, [])
return <BalancesContext.Provider value={[state, { update }]}>{children}</BalancesContext.Provider> return (
<BalancesContext.Provider value={useMemo(() => [state, { update }], [state, update])}>
{children}
</BalancesContext.Provider>
)
} }
export function useAddressBalance(address, tokenAddress) { export function useAddressBalance(address, tokenAddress) {
......
import React, { createContext, useContext, useReducer, useCallback, useEffect } from 'react' import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
import { useWeb3Context } from 'web3-react' import { useWeb3Context } from 'web3-react'
import { ethers } from 'ethers' import { ethers } from 'ethers'
...@@ -59,6 +59,12 @@ const INITIAL_TOKENS_CONTEXT = { ...@@ -59,6 +59,12 @@ const INITIAL_TOKENS_CONTEXT = {
[DECIMALS]: 18, [DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xF7B5A4b934658025390ff69dB302BC7F2AC4a542' [EXCHANGE_ADDRESS]: '0xF7B5A4b934658025390ff69dB302BC7F2AC4a542'
}, },
'0xF5DCe57282A584D2746FaF1593d3121Fcac444dC': {
[NAME]: 'Compound Dai',
[SYMBOL]: 'cDAI',
[DECIMALS]: 8,
[EXCHANGE_ADDRESS]: '0x45A2FDfED7F7a2c791fb1bdF6075b83faD821ddE'
},
'0x41e5560054824eA6B0732E656E3Ad64E20e94E45': { '0x41e5560054824eA6B0732E656E3Ad64E20e94E45': {
[NAME]: 'Civic', [NAME]: 'Civic',
[SYMBOL]: 'CVC', [SYMBOL]: 'CVC',
...@@ -101,12 +107,6 @@ const INITIAL_TOKENS_CONTEXT = { ...@@ -101,12 +107,6 @@ const INITIAL_TOKENS_CONTEXT = {
[DECIMALS]: 12, [DECIMALS]: 12,
[EXCHANGE_ADDRESS]: '0x4B17685b330307C751B47f33890c8398dF4Fe407' [EXCHANGE_ADDRESS]: '0x4B17685b330307C751B47f33890c8398dF4Fe407'
}, },
'0x056Fd409E1d7A124BD7017459dFEa2F387b6d5Cd': {
[NAME]: 'Gemini dollar',
[SYMBOL]: 'GUSD',
[DECIMALS]: 2,
[EXCHANGE_ADDRESS]: '0xD883264737Ed969d2696eE4B4cAF529c2Fc2A141'
},
'0x818Fc6C2Ec5986bc6E2CBf00939d90556aB12ce5': { '0x818Fc6C2Ec5986bc6E2CBf00939d90556aB12ce5': {
[NAME]: 'Kin', [NAME]: 'Kin',
[SYMBOL]: 'KIN', [SYMBOL]: 'KIN',
...@@ -125,6 +125,12 @@ const INITIAL_TOKENS_CONTEXT = { ...@@ -125,6 +125,12 @@ const INITIAL_TOKENS_CONTEXT = {
[DECIMALS]: 18, [DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xF173214C720f58E03e194085B1DB28B50aCDeeaD' [EXCHANGE_ADDRESS]: '0xF173214C720f58E03e194085B1DB28B50aCDeeaD'
}, },
'0xBBbbCA6A901c926F240b89EacB641d8Aec7AEafD': {
[NAME]: 'LoopringCoin V2',
[SYMBOL]: 'LRC',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xA539BAaa3aCA455c986bB1E25301CEF936CE1B65'
},
'0x6c6EE5e31d828De241282B9606C8e98Ea48526E2': { '0x6c6EE5e31d828De241282B9606C8e98Ea48526E2': {
[NAME]: 'HoloToken', [NAME]: 'HoloToken',
[SYMBOL]: 'HOT', [SYMBOL]: 'HOT',
...@@ -233,12 +239,6 @@ const INITIAL_TOKENS_CONTEXT = { ...@@ -233,12 +239,6 @@ const INITIAL_TOKENS_CONTEXT = {
[DECIMALS]: 18, [DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x1aEC8F11A7E78dC22477e91Ed924Fab46e3A88Fd' [EXCHANGE_ADDRESS]: '0x1aEC8F11A7E78dC22477e91Ed924Fab46e3A88Fd'
}, },
'0xEf8a2c1BC94e630463293F71bF5414d13e80F62D': {
[NAME]: 'Synthetix Network Token',
[SYMBOL]: 'SNX',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xd9025Ed64BAA7B9046E37fe94670C79fcCB2b5C8'
},
'0x42d6622deCe394b54999Fbd73D108123806f6a18': { '0x42d6622deCe394b54999Fbd73D108123806f6a18': {
[NAME]: 'SPANK', [NAME]: 'SPANK',
[SYMBOL]: 'SPANK', [SYMBOL]: 'SPANK',
...@@ -293,12 +293,6 @@ const INITIAL_TOKENS_CONTEXT = { ...@@ -293,12 +293,6 @@ const INITIAL_TOKENS_CONTEXT = {
[DECIMALS]: 18, [DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x8dE0d002DC83478f479dC31F76cB0a8aa7CcEa17' [EXCHANGE_ADDRESS]: '0x8dE0d002DC83478f479dC31F76cB0a8aa7CcEa17'
}, },
'0x05f4a42e251f2d52b8ed15E9FEdAacFcEF1FAD27': {
[NAME]: 'Zilliqa',
[SYMBOL]: 'ZIL',
[DECIMALS]: 12,
[EXCHANGE_ADDRESS]: '0x7dc095A5CF7D6208CC680fA9866F80a53911041a'
},
'0xE41d2489571d322189246DaFA5ebDe1F4699F498': { '0xE41d2489571d322189246DaFA5ebDe1F4699F498': {
[NAME]: '0x Protocol Token', [NAME]: '0x Protocol Token',
[SYMBOL]: 'ZRX', [SYMBOL]: 'ZRX',
...@@ -344,7 +338,11 @@ export default function Provider({ children }) { ...@@ -344,7 +338,11 @@ export default function Provider({ children }) {
dispatch({ type: UPDATE, payload: { networkId, tokenAddress, name, symbol, decimals, exchangeAddress } }) dispatch({ type: UPDATE, payload: { networkId, tokenAddress, name, symbol, decimals, exchangeAddress } })
}, []) }, [])
return <TokensContext.Provider value={[state, { update }]}>{children}</TokensContext.Provider> return (
<TokensContext.Provider value={useMemo(() => [state, { update }], [state, update])}>
{children}
</TokensContext.Provider>
)
} }
export function useTokenDetails(tokenAddress) { export function useTokenDetails(tokenAddress) {
......
import React, { createContext, useContext, useReducer, useCallback, useEffect } from 'react' import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
import { useWeb3Context } from 'web3-react' import { useWeb3Context } from 'web3-react'
import { ethers } from 'ethers'
import { safeAccess } from '../utils' import { safeAccess } from '../utils'
import { useBlockNumber } from './Application' import { useBlockNumber } from './Application'
const RESPONSE = 'response' const RESPONSE = 'response'
const CUSTOM_DATA = 'CUSTOM_DATA'
const BLOCK_NUMBER_CHECKED = 'BLOCK_NUMBER_CHECKED' const BLOCK_NUMBER_CHECKED = 'BLOCK_NUMBER_CHECKED'
const RECEIPT = 'receipt' const RECEIPT = 'receipt'
...@@ -94,7 +94,11 @@ export default function Provider({ children }) { ...@@ -94,7 +94,11 @@ export default function Provider({ children }) {
}, []) }, [])
return ( return (
<TransactionsContext.Provider value={[state, { add, check, finalize }]}>{children}</TransactionsContext.Provider> <TransactionsContext.Provider
value={useMemo(() => [state, { add, check, finalize }], [state, add, check, finalize])}
>
{children}
</TransactionsContext.Provider>
) )
} }
...@@ -145,7 +149,7 @@ export function useTransactionAdder() { ...@@ -145,7 +149,7 @@ export function useTransactionAdder() {
const [, { add }] = useTransactionsContext() const [, { add }] = useTransactionsContext()
return useCallback( return useCallback(
response => { (response, customData = {}) => {
if (!(networkId || networkId === 0)) { if (!(networkId || networkId === 0)) {
throw Error(`Invalid networkId '${networkId}`) throw Error(`Invalid networkId '${networkId}`)
} }
...@@ -155,7 +159,7 @@ export function useTransactionAdder() { ...@@ -155,7 +159,7 @@ export function useTransactionAdder() {
if (!hash) { if (!hash) {
throw Error('No transaction hash found.') throw Error('No transaction hash found.')
} }
add(networkId, hash, response) add(networkId, hash, { ...response, [CUSTOM_DATA]: customData })
}, },
[networkId, add] [networkId, add]
) )
...@@ -178,12 +182,7 @@ export function usePendingApproval(tokenAddress) { ...@@ -178,12 +182,7 @@ export function usePendingApproval(tokenAddress) {
return false return false
} else if (!allTransactions[hash][RESPONSE]) { } else if (!allTransactions[hash][RESPONSE]) {
return false return false
} else if (allTransactions[hash][RESPONSE].to !== tokenAddress) { } else if (allTransactions[hash][RESPONSE][CUSTOM_DATA].approval !== tokenAddress) {
return false
} else if (
allTransactions[hash][RESPONSE].data.substring(0, 10) !==
ethers.utils.id('approve(address,uint256)').substring(0, 10)
) {
return false return false
} else { } else {
return true return true
......
...@@ -305,7 +305,7 @@ export default function AddLiquidity() { ...@@ -305,7 +305,7 @@ export default function AddLiquidity() {
<> <>
<div> <div>
{t('youAreAdding')} {b(`${amountFormatter(inputValueParsed, 18, 4)} ETH`)} {t('and')} {'at most'}{' '} {t('youAreAdding')} {b(`${amountFormatter(inputValueParsed, 18, 4)} ETH`)} {t('and')} {'at most'}{' '}
{b(`${amountFormatter(outputValueMax, 18, 4)} ${symbol}`)} {t('intoPool')} {b(`${amountFormatter(outputValueMax, decimals, 4)} ${symbol}`)} {t('intoPool')}
</div> </div>
<div> <div>
{t('youWillMint')} {b(amountFormatter(liquidityMinted, 18, 4))} {t('liquidityTokens')} {t('youWillMint')} {b(amountFormatter(liquidityMinted, 18, 4))} {t('liquidityTokens')}
...@@ -405,7 +405,7 @@ export default function AddLiquidity() { ...@@ -405,7 +405,7 @@ export default function AddLiquidity() {
// parse input value // parse input value
useEffect(() => { useEffect(() => {
if (isNewExchange === false && inputValue && marketRate && lastEditedField === INPUT) { if (isNewExchange === false && inputValue && marketRate && lastEditedField === INPUT && decimals) {
try { try {
const parsedValue = ethers.utils.parseUnits(inputValue, 18) const parsedValue = ethers.utils.parseUnits(inputValue, 18)
...@@ -418,10 +418,12 @@ export default function AddLiquidity() { ...@@ -418,10 +418,12 @@ export default function AddLiquidity() {
const currencyAmount = marketRate const currencyAmount = marketRate
.mul(parsedValue) .mul(parsedValue)
.div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18))) .div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18)))
.div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18 - decimals)))
setOutputValueParsed(currencyAmount) setOutputValueParsed(currencyAmount)
dispatchAddLiquidityState({ dispatchAddLiquidityState({
type: 'UPDATE_DEPENDENT_VALUE', type: 'UPDATE_DEPENDENT_VALUE',
payload: { field: OUTPUT, value: amountFormatter(currencyAmount, 18, 4, false) } payload: { field: OUTPUT, value: amountFormatter(currencyAmount, decimals, 4, false) }
}) })
return () => { return () => {
...@@ -437,13 +439,13 @@ export default function AddLiquidity() { ...@@ -437,13 +439,13 @@ export default function AddLiquidity() {
setOutputError(t('inputNotValid')) setOutputError(t('inputNotValid'))
} }
} }
}, [inputValue, isNewExchange, lastEditedField, marketRate, t]) }, [inputValue, isNewExchange, lastEditedField, marketRate, decimals, t])
// parse output value // parse output value
useEffect(() => { useEffect(() => {
if (isNewExchange === false && outputValue && marketRateInverted && lastEditedField === OUTPUT) { if (isNewExchange === false && outputValue && marketRateInverted && lastEditedField === OUTPUT && decimals) {
try { try {
const parsedValue = ethers.utils.parseUnits(outputValue, 18) const parsedValue = ethers.utils.parseUnits(outputValue, decimals)
if (parsedValue.lte(ethers.constants.Zero) || parsedValue.gte(ethers.constants.MaxUint256)) { if (parsedValue.lte(ethers.constants.Zero) || parsedValue.gte(ethers.constants.MaxUint256)) {
throw Error() throw Error()
...@@ -453,7 +455,8 @@ export default function AddLiquidity() { ...@@ -453,7 +455,8 @@ export default function AddLiquidity() {
const currencyAmount = marketRateInverted const currencyAmount = marketRateInverted
.mul(parsedValue) .mul(parsedValue)
.div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18))) .div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(decimals)))
setInputValueParsed(currencyAmount) setInputValueParsed(currencyAmount)
dispatchAddLiquidityState({ dispatchAddLiquidityState({
type: 'UPDATE_DEPENDENT_VALUE', type: 'UPDATE_DEPENDENT_VALUE',
...@@ -473,7 +476,7 @@ export default function AddLiquidity() { ...@@ -473,7 +476,7 @@ export default function AddLiquidity() {
setInputError(t('inputNotValid')) setInputError(t('inputNotValid'))
} }
} }
}, [outputValue, isNewExchange, lastEditedField, marketRateInverted, t]) }, [outputValue, isNewExchange, lastEditedField, marketRateInverted, decimals, t])
// input validation // input validation
useEffect(() => { useEffect(() => {
...@@ -485,14 +488,14 @@ export default function AddLiquidity() { ...@@ -485,14 +488,14 @@ export default function AddLiquidity() {
} }
} }
if (outputValueParsed && outputBalance) { if (outputValueMax && outputBalance) {
if (outputValueParsed.gt(outputBalance)) { if (outputValueMax.gt(outputBalance)) {
setOutputError(t('insufficientBalance')) setOutputError(t('insufficientBalance'))
} else { } else {
setOutputError(null) setOutputError(null)
} }
} }
}, [inputValueParsed, inputBalance, outputValueParsed, outputBalance, t]) }, [inputValueParsed, inputBalance, outputValueMax, outputBalance, t])
const allowance = useAddressAllowance(account, outputCurrency, exchangeAddress) const allowance = useAddressAllowance(account, outputCurrency, exchangeAddress)
const [showUnlock, setShowUnlock] = useState(false) const [showUnlock, setShowUnlock] = useState(false)
...@@ -590,7 +593,7 @@ export default function AddLiquidity() { ...@@ -590,7 +593,7 @@ export default function AddLiquidity() {
</OversizedPanel> </OversizedPanel>
{renderSummary()} {renderSummary()}
<Flex> <Flex>
<Button disabled={!isValid} onClick={onAddLiquidity} fullWidth> <Button disabled={!isValid} onClick={onAddLiquidity}>
{t('addLiquidity')} {t('addLiquidity')}
</Button> </Button>
</Flex> </Flex>
......
...@@ -142,7 +142,7 @@ function CreateExchange({ history, location }) { ...@@ -142,7 +142,7 @@ function CreateExchange({ history, location }) {
<SummaryText>{errorMessage ? errorMessage : t('enterTokenCont')}</SummaryText> <SummaryText>{errorMessage ? errorMessage : t('enterTokenCont')}</SummaryText>
</CreateExchangeWrapper> </CreateExchangeWrapper>
<Flex> <Flex>
<Button disabled={!isValid} onClick={createExchange} fullWidth> <Button disabled={!isValid} onClick={createExchange}>
{t('createExchange')} {t('createExchange')}
</Button> </Button>
</Flex> </Flex>
......
...@@ -206,13 +206,13 @@ export default function RemoveLiquidity() { ...@@ -206,13 +206,13 @@ export default function RemoveLiquidity() {
: undefined : undefined
const ethWithdrawn = const ethWithdrawn =
ETHPer && ETHPer && valueParsed
valueParsed && ? ETHPer.mul(valueParsed).div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18)))
ETHPer.mul(valueParsed).div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18))) : undefined
const tokenWithdrawn = const tokenWithdrawn =
tokenPer && tokenPer && valueParsed
valueParsed && ? tokenPer.mul(valueParsed).div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18)))
tokenPer.mul(valueParsed).div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18))) : undefined
const ethWithdrawnMin = ethWithdrawn ? calculateSlippageBounds(ethWithdrawn).minimum : undefined const ethWithdrawnMin = ethWithdrawn ? calculateSlippageBounds(ethWithdrawn).minimum : undefined
const tokenWithdrawnMin = tokenWithdrawn ? calculateSlippageBounds(tokenWithdrawn).minimum : undefined const tokenWithdrawnMin = tokenWithdrawn ? calculateSlippageBounds(tokenWithdrawn).minimum : undefined
...@@ -348,17 +348,17 @@ export default function RemoveLiquidity() { ...@@ -348,17 +348,17 @@ export default function RemoveLiquidity() {
</OversizedPanel> </OversizedPanel>
<CurrencyInputPanel <CurrencyInputPanel
title={t('output')} title={t('output')}
description={ethWithdrawn && tokenWithdrawn ? `(${t('estimated')})` : ''} description={ethWithdrawnMin && tokenWithdrawnMin ? `(${t('estimated')})` : ''}
key="remove-liquidity-input" key="remove-liquidity-input"
renderInput={() => renderInput={() =>
ethWithdrawn && tokenWithdrawn ? ( ethWithdrawnMin && tokenWithdrawnMin ? (
<RemoveLiquidityOutput> <RemoveLiquidityOutput>
<RemoveLiquidityOutputText> <RemoveLiquidityOutputText>
{`${amountFormatter(ethWithdrawn, 18, 4, false)} ETH`} {`${amountFormatter(ethWithdrawnMin, 18, 4, false)} ETH`}
</RemoveLiquidityOutputText> </RemoveLiquidityOutputText>
<RemoveLiquidityOutputPlus> + </RemoveLiquidityOutputPlus> <RemoveLiquidityOutputPlus> + </RemoveLiquidityOutputPlus>
<RemoveLiquidityOutputText> <RemoveLiquidityOutputText>
{`${amountFormatter(tokenWithdrawn, decimals, Math.min(4, decimals))} ${symbol}`} {`${amountFormatter(tokenWithdrawnMin, decimals, Math.min(4, decimals))} ${symbol}`}
</RemoveLiquidityOutputText> </RemoveLiquidityOutputText>
</RemoveLiquidityOutput> </RemoveLiquidityOutput>
) : ( ) : (
...@@ -406,7 +406,7 @@ export default function RemoveLiquidity() { ...@@ -406,7 +406,7 @@ export default function RemoveLiquidity() {
</OversizedPanel> </OversizedPanel>
{renderSummary()} {renderSummary()}
<Flex> <Flex>
<Button disabled={!isValid} onClick={onRemoveLiquidity} fullWidth> <Button disabled={!isValid} onClick={onRemoveLiquidity}>
{t('removeLiquidity')} {t('removeLiquidity')}
</Button> </Button>
</Flex> </Flex>
......
...@@ -486,7 +486,11 @@ export default function Swap() { ...@@ -486,7 +486,11 @@ export default function Swap() {
.sub(ethers.utils.bigNumberify(3).mul(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(15)))) .sub(ethers.utils.bigNumberify(3).mul(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(15))))
: undefined : undefined
const percentSlippageFormatted = percentSlippage && amountFormatter(percentSlippage, 16, 2) const percentSlippageFormatted = percentSlippage && amountFormatter(percentSlippage, 16, 2)
const slippageWarning = percentSlippage && percentSlippage.gte(ethers.utils.parseEther('.1')) // 10% const slippageWarning =
percentSlippage &&
percentSlippage.gte(ethers.utils.parseEther('.05')) &&
percentSlippage.lt(ethers.utils.parseEther('.2')) // [5% - 20%)
const highSlippageWarning = percentSlippage && percentSlippage.gte(ethers.utils.parseEther('.2')) // [20+%
const isValid = exchangeRate && inputError === null && independentError === null && recipientError === null const isValid = exchangeRate && inputError === null && independentError === null && recipientError === null
...@@ -529,6 +533,11 @@ export default function Swap() { ...@@ -529,6 +533,11 @@ export default function Swap() {
{t('orTransFail')} {t('orTransFail')}
</LastSummaryText> </LastSummaryText>
<LastSummaryText> <LastSummaryText>
{(slippageWarning || highSlippageWarning) && (
<span role="img" aria-label="warning">
⚠️
</span>
)}
{t('priceChange')} {b(`${percentSlippageFormatted}%`)}. {t('priceChange')} {b(`${percentSlippageFormatted}%`)}.
</LastSummaryText> </LastSummaryText>
</div> </div>
...@@ -586,13 +595,21 @@ export default function Swap() { ...@@ -586,13 +595,21 @@ export default function Swap() {
isError = true isError = true
} }
const slippageWarningText = highSlippageWarning
? t('highSlippageWarning')
: slippageWarning
? t('slippageWarning')
: ''
return ( return (
<NewContextualInfo <NewContextualInfo
openDetailsText={t('transactionDetails')} openDetailsText={t('transactionDetails')}
closeDetailsText={t('hideDetails')} closeDetailsText={t('hideDetails')}
contextualInfo={contextualInfo ? contextualInfo : slippageWarning ? t('slippageWarning') : ''} contextualInfo={contextualInfo ? contextualInfo : slippageWarningText}
allowExpand={!!(inputCurrency && outputCurrency && inputValueParsed && outputValueParsed && recipient.address)} allowExpand={!!(inputCurrency && outputCurrency && inputValueParsed && outputValueParsed && recipient.address)}
isError={isError} isError={isError}
slippageWarning={slippageWarning && slippageWarningText}
highSlippageWarning={highSlippageWarning && slippageWarningText}
renderTransactionDetails={renderTransactionDetails} renderTransactionDetails={renderTransactionDetails}
/> />
) )
...@@ -755,8 +772,8 @@ export default function Swap() { ...@@ -755,8 +772,8 @@ export default function Swap() {
</OversizedPanel> </OversizedPanel>
{renderSummary()} {renderSummary()}
<Flex> <Flex>
<Button disabled={!isValid} onClick={onSwap} fullWidth> <Button disabled={!isValid} onClick={onSwap} warning={highSlippageWarning}>
{t('swap')} {highSlippageWarning ? t('sendAnyway') : t('send')}
</Button> </Button>
</Flex> </Flex>
</> </>
......
...@@ -481,7 +481,11 @@ export default function Swap() { ...@@ -481,7 +481,11 @@ export default function Swap() {
.sub(ethers.utils.bigNumberify(3).mul(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(15)))) .sub(ethers.utils.bigNumberify(3).mul(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(15))))
: undefined : undefined
const percentSlippageFormatted = percentSlippage && amountFormatter(percentSlippage, 16, 2) const percentSlippageFormatted = percentSlippage && amountFormatter(percentSlippage, 16, 2)
const slippageWarning = percentSlippage && percentSlippage.gte(ethers.utils.parseEther('.1')) // 10% const slippageWarning =
percentSlippage &&
percentSlippage.gte(ethers.utils.parseEther('.05')) &&
percentSlippage.lt(ethers.utils.parseEther('.2')) // [5% - 20%)
const highSlippageWarning = percentSlippage && percentSlippage.gte(ethers.utils.parseEther('.2')) // [20+%
const isValid = exchangeRate && inputError === null && independentError === null const isValid = exchangeRate && inputError === null && independentError === null
...@@ -524,6 +528,11 @@ export default function Swap() { ...@@ -524,6 +528,11 @@ export default function Swap() {
{t('orTransFail')} {t('orTransFail')}
</LastSummaryText> </LastSummaryText>
<LastSummaryText> <LastSummaryText>
{(slippageWarning || highSlippageWarning) && (
<span role="img" aria-label="warning">
⚠️
</span>
)}
{t('priceChange')} {b(`${percentSlippageFormatted}%`)}. {t('priceChange')} {b(`${percentSlippageFormatted}%`)}.
</LastSummaryText> </LastSummaryText>
</div> </div>
...@@ -577,13 +586,21 @@ export default function Swap() { ...@@ -577,13 +586,21 @@ export default function Swap() {
isError = true isError = true
} }
const slippageWarningText = highSlippageWarning
? t('highSlippageWarning')
: slippageWarning
? t('slippageWarning')
: ''
return ( return (
<NewContextualInfo <NewContextualInfo
openDetailsText={t('transactionDetails')} openDetailsText={t('transactionDetails')}
closeDetailsText={t('hideDetails')} closeDetailsText={t('hideDetails')}
contextualInfo={contextualInfo ? contextualInfo : slippageWarning ? t('slippageWarning') : ''} contextualInfo={contextualInfo ? contextualInfo : slippageWarningText}
allowExpand={!!(inputCurrency && outputCurrency && inputValueParsed && outputValueParsed)} allowExpand={!!(inputCurrency && outputCurrency && inputValueParsed && outputValueParsed)}
isError={isError} isError={isError}
slippageWarning={slippageWarning && slippageWarningText}
highSlippageWarning={highSlippageWarning && slippageWarningText}
renderTransactionDetails={renderTransactionDetails} renderTransactionDetails={renderTransactionDetails}
/> />
) )
...@@ -726,8 +743,8 @@ export default function Swap() { ...@@ -726,8 +743,8 @@ export default function Swap() {
</OversizedPanel> </OversizedPanel>
{renderSummary()} {renderSummary()}
<Flex> <Flex>
<Button disabled={!isValid} onClick={onSwap}> <Button disabled={!isValid} onClick={onSwap} warning={highSlippageWarning}>
{t('swap')} {highSlippageWarning ? t('swapAnyway') : t('swap')}
</Button> </Button>
</Flex> </Flex>
</> </>
......
import styled from 'styled-components' import styled from 'styled-components'
import { lighten, darken } from 'polished' import { lighten, darken } from 'polished'
export const Button = styled.button` export const Button = styled.button.attrs({
backgroundColor: ({ warning, theme }) => (warning ? theme.salmonRed : theme.royalBlue)
})`
padding: 1rem 2rem 1rem 2rem; padding: 1rem 2rem 1rem 2rem;
border-radius: 3rem; border-radius: 3rem;
cursor: pointer; cursor: pointer;
...@@ -9,18 +11,18 @@ export const Button = styled.button` ...@@ -9,18 +11,18 @@ export const Button = styled.button`
font-size: 1rem; font-size: 1rem;
border: none; border: none;
outline: none; outline: none;
background-color: ${({ theme }) => theme.royalBlue}; background-color: ${({ backgroundColor }) => backgroundColor};
color: ${({ theme }) => theme.white}; color: ${({ theme }) => theme.white};
transition: background-color 125ms ease-in-out; transition: background-color 125ms ease-in-out;
width: 100%; width: 100%;
:hover, :hover,
:focus { :focus {
background-color: ${({ theme }) => lighten(0.05, theme.royalBlue)}; background-color: ${({ backgroundColor }) => lighten(0.05, backgroundColor)};
} }
:active { :active {
background-color: ${({ theme }) => darken(0.05, theme.royalBlue)}; background-color: ${({ backgroundColor }) => darken(0.05, backgroundColor)};
} }
:disabled { :disabled {
......
...@@ -57,6 +57,8 @@ const theme = { ...@@ -57,6 +57,8 @@ const theme = {
salmonRed: '#FF6871', salmonRed: '#FF6871',
// orange // orange
pizazzOrange: '#FF8F05', pizazzOrange: '#FF8F05',
// yellows
warningYellow: '#FFE270',
// pink // pink
uniswapPink: '#DC6BE5', uniswapPink: '#DC6BE5',
connectedGreen: '#27AE60', connectedGreen: '#27AE60',
......
...@@ -6,6 +6,8 @@ import ERC20_ABI from '../constants/abis/erc20' ...@@ -6,6 +6,8 @@ import ERC20_ABI from '../constants/abis/erc20'
import ERC20_BYTES32_ABI from '../constants/abis/erc20_bytes32' import ERC20_BYTES32_ABI from '../constants/abis/erc20_bytes32'
import { FACTORY_ADDRESSES } from '../constants' import { FACTORY_ADDRESSES } from '../constants'
import UncheckedJsonRpcSigner from './signer'
export const ERROR_CODES = ['TOKEN_NAME', 'TOKEN_SYMBOL', 'TOKEN_DECIMALS'].reduce( export const ERROR_CODES = ['TOKEN_NAME', 'TOKEN_SYMBOL', 'TOKEN_DECIMALS'].reduce(
(accumulator, currentValue, currentIndex) => { (accumulator, currentValue, currentIndex) => {
accumulator[currentValue] = currentIndex accumulator[currentValue] = currentIndex
...@@ -93,7 +95,7 @@ export function calculateGasMargin(value, margin) { ...@@ -93,7 +95,7 @@ export function calculateGasMargin(value, margin) {
// account is optional // account is optional
export function getProviderOrSigner(library, account) { export function getProviderOrSigner(library, account) {
return account ? library.getSigner(account) : library return account ? new UncheckedJsonRpcSigner(library.getSigner(account)) : library
} }
// account is optional // account is optional
......
import * as ethers from 'ethers'
export default class UncheckedJsonRpcSigner extends ethers.Signer {
constructor(signer) {
super()
ethers.utils.defineReadOnly(this, 'signer', signer)
ethers.utils.defineReadOnly(this, 'provider', signer.provider)
}
getAddress() {
return this.signer.getAddress()
}
sendTransaction(transaction) {
return this.signer.sendUncheckedTransaction(transaction).then(hash => {
return {
hash: hash,
nonce: null,
gasLimit: null,
gasPrice: null,
data: null,
value: null,
chainId: null,
confirmations: 0,
from: null,
wait: confirmations => {
return this.signer.provider.waitForTransaction(hash, confirmations)
}
}
})
}
signMessage(message) {
return this.signer.signMessage(message)
}
}
...@@ -4715,7 +4715,23 @@ ethereumjs-wallet@0.6.2: ...@@ -4715,7 +4715,23 @@ ethereumjs-wallet@0.6.2:
utf8 "^3.0.0" utf8 "^3.0.0"
uuid "^3.3.2" uuid "^3.3.2"
ethers@^4.0.27, ethers@~4.0.4: ethers@^4.0.28:
version "4.0.28"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.28.tgz#74d9acb57f4ede3337c8d60476b38d0fe646af01"
integrity sha512-5JTHrPoFLqf+xCAI3pKwXSOgWBSJJoAUdPtPLr1ZlKbSKiIFMkPlRNovmZS3jhIw5sHW1YoVWOaJ6ZR2gKRbwg==
dependencies:
"@types/node" "^10.3.2"
aes-js "3.0.0"
bn.js "^4.4.0"
elliptic "6.3.3"
hash.js "1.1.3"
js-sha3 "0.5.7"
scrypt-js "2.0.4"
setimmediate "1.0.4"
uuid "2.0.1"
xmlhttprequest "1.8.0"
ethers@~4.0.4:
version "4.0.27" version "4.0.27"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.27.tgz#e570b0da9d805ad65c83d81919abe02b2264c6bf" resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.27.tgz#e570b0da9d805ad65c83d81919abe02b2264c6bf"
dependencies: dependencies:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment