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
This commit is contained in:
Noah Zinsmeister 2019-05-28 14:10:02 -04:00 committed by GitHub
parent 6579e17f7f
commit f2f960f6fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 231 additions and 92 deletions

@ -14,7 +14,7 @@
"@reach/tooltip": "^0.2.0",
"copy-to-clipboard": "^3.2.0",
"escape-string-regexp": "^2.0.0",
"ethers": "^4.0.27",
"ethers": "^4.0.28",
"i18next": "^15.0.9",
"i18next-browser-languagedetector": "^3.0.1",
"i18next-xhr-backend": "^2.0.1",

@ -6,7 +6,9 @@
"installMetamask": "Please visit us after installing Metamask on Chrome or Brave.",
"disconnected": "Disconnected",
"swap": "Swap",
"swapAnyway": "Swap Anyway",
"send": "Send",
"sendAnyway": "Send Anyway",
"pool": "Pool",
"betaWarning": "This project is in beta. Use at your own risk.",
"input": "Input",
@ -28,6 +30,7 @@
"transactionDetails": "Transaction Details",
"hideDetails": "Hide Details",
"slippageWarning": "Slippage Warning",
"highSlippageWarning": "High Slippage Warning",
"youAreSelling": "You are selling",
"orTransFail": "or the transaction will fail.",
"youWillReceive": "You will receive at least",

@ -1,5 +1,6 @@
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 Dropdown } from '../../assets/images/dropdown-blue.svg'
@ -23,10 +24,6 @@ const SummaryWrapperContainer = styled.div`
justify-content: center;
font-size: 0.75rem;
span {
margin-right: 12px;
}
img {
height: 0.75rem;
width: 0.75rem;
@ -42,13 +39,33 @@ const Details = styled.div`
`
const ErrorSpan = styled.span`
margin-right: 12px;
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 ColoredDropup = styled(WrappedDropup)`
path {
stroke: ${({ isError, theme }) => isError && theme.salmonRed};
${({ highSlippageWarning, theme }) =>
highSlippageWarning &&
css`
stroke: ${theme.salmonRed};
`}
}
`
@ -56,6 +73,12 @@ const WrappedDropdown = ({ isError, ...rest }) => <Dropdown {...rest} />
const ColoredDropdown = styled(WrappedDropdown)`
path {
stroke: ${({ isError, theme }) => isError && theme.salmonRed};
${({ highSlippageWarning, theme }) =>
highSlippageWarning &&
css`
stroke: ${theme.salmonRed};
`}
}
`
@ -65,7 +88,9 @@ export default function ContextualInfo({
contextualInfo = '',
allowExpand = false,
renderTransactionDetails = () => {},
isError = false
isError = false,
slippageWarning,
highSlippageWarning
}) {
const [showDetails, setShowDetails] = useState(false)
@ -75,10 +100,19 @@ export default function ContextualInfo({
<>
<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}
</ErrorSpan>
{showDetails ? <ColoredDropup isError={isError} /> : <ColoredDropdown isError={isError} />}
{showDetails ? (
<ColoredDropup isError={isError} highSlippageWarning={highSlippageWarning} />
) : (
<ColoredDropdown isError={isError} highSlippageWarning={highSlippageWarning} />
)}
</>
</SummaryWrapperContainer>
{showDetails && <Details>{renderTransactionDetails()}</Details>}

@ -236,7 +236,7 @@ export default function CurrencyInputPanel({
gasLimit: calculateGasMargin(estimatedGas, GAS_MARGIN)
})
.then(response => {
addTransaction(response)
addTransaction(response, { approval: selectedTokenAddress })
})
}}
>

@ -88,18 +88,14 @@ const StyledNavLink = styled(NavLink).attrs({
font-weight: 500;
color: ${({ theme }) => theme.royalBlue};
:hover {
box-shadow: 0 0 0.5px 1px ${({ theme }) => darken(0.1, theme.mercuryGray)};
}
box-shadow: 0 0 0.5px 1px ${({ theme }) => darken(0.1, theme.mercuryGray)};
}
}
:hover,
:focus {
font-weight: 500;
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;
}
`

@ -1,4 +1,4 @@
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 { safeAccess, isAddress, getTokenAllowance } from '../utils'
@ -46,7 +46,11 @@ export default function Provider({ children }) {
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) {

@ -1,4 +1,4 @@
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 { safeAccess } from '../utils'
@ -52,7 +52,13 @@ export default function Provider({ children }) {
}, [])
return (
<ApplicationContext.Provider value={[state, { dismissBetaMessage, updateBlockNumber }]}>
<ApplicationContext.Provider
value={useMemo(() => [state, { dismissBetaMessage, updateBlockNumber }], [
state,
dismissBetaMessage,
updateBlockNumber
])}
>
{children}
</ApplicationContext.Provider>
)

@ -1,4 +1,4 @@
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 { safeAccess, isAddress, getEtherBalance, getTokenBalance } from '../utils'
@ -44,7 +44,11 @@ export default function Provider({ children }) {
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) {

@ -1,4 +1,4 @@
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 { ethers } from 'ethers'
@ -59,6 +59,12 @@ const INITIAL_TOKENS_CONTEXT = {
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xF7B5A4b934658025390ff69dB302BC7F2AC4a542'
},
'0xF5DCe57282A584D2746FaF1593d3121Fcac444dC': {
[NAME]: 'Compound Dai',
[SYMBOL]: 'cDAI',
[DECIMALS]: 8,
[EXCHANGE_ADDRESS]: '0x45A2FDfED7F7a2c791fb1bdF6075b83faD821ddE'
},
'0x41e5560054824eA6B0732E656E3Ad64E20e94E45': {
[NAME]: 'Civic',
[SYMBOL]: 'CVC',
@ -101,12 +107,6 @@ const INITIAL_TOKENS_CONTEXT = {
[DECIMALS]: 12,
[EXCHANGE_ADDRESS]: '0x4B17685b330307C751B47f33890c8398dF4Fe407'
},
'0x056Fd409E1d7A124BD7017459dFEa2F387b6d5Cd': {
[NAME]: 'Gemini dollar',
[SYMBOL]: 'GUSD',
[DECIMALS]: 2,
[EXCHANGE_ADDRESS]: '0xD883264737Ed969d2696eE4B4cAF529c2Fc2A141'
},
'0x818Fc6C2Ec5986bc6E2CBf00939d90556aB12ce5': {
[NAME]: 'Kin',
[SYMBOL]: 'KIN',
@ -125,6 +125,12 @@ const INITIAL_TOKENS_CONTEXT = {
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xF173214C720f58E03e194085B1DB28B50aCDeeaD'
},
'0xBBbbCA6A901c926F240b89EacB641d8Aec7AEafD': {
[NAME]: 'LoopringCoin V2',
[SYMBOL]: 'LRC',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xA539BAaa3aCA455c986bB1E25301CEF936CE1B65'
},
'0x6c6EE5e31d828De241282B9606C8e98Ea48526E2': {
[NAME]: 'HoloToken',
[SYMBOL]: 'HOT',
@ -233,12 +239,6 @@ const INITIAL_TOKENS_CONTEXT = {
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x1aEC8F11A7E78dC22477e91Ed924Fab46e3A88Fd'
},
'0xEf8a2c1BC94e630463293F71bF5414d13e80F62D': {
[NAME]: 'Synthetix Network Token',
[SYMBOL]: 'SNX',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xd9025Ed64BAA7B9046E37fe94670C79fcCB2b5C8'
},
'0x42d6622deCe394b54999Fbd73D108123806f6a18': {
[NAME]: 'SPANK',
[SYMBOL]: 'SPANK',
@ -293,12 +293,6 @@ const INITIAL_TOKENS_CONTEXT = {
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x8dE0d002DC83478f479dC31F76cB0a8aa7CcEa17'
},
'0x05f4a42e251f2d52b8ed15E9FEdAacFcEF1FAD27': {
[NAME]: 'Zilliqa',
[SYMBOL]: 'ZIL',
[DECIMALS]: 12,
[EXCHANGE_ADDRESS]: '0x7dc095A5CF7D6208CC680fA9866F80a53911041a'
},
'0xE41d2489571d322189246DaFA5ebDe1F4699F498': {
[NAME]: '0x Protocol Token',
[SYMBOL]: 'ZRX',
@ -344,7 +338,11 @@ export default function Provider({ children }) {
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) {

@ -1,11 +1,11 @@
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 { ethers } from 'ethers'
import { safeAccess } from '../utils'
import { useBlockNumber } from './Application'
const RESPONSE = 'response'
const CUSTOM_DATA = 'CUSTOM_DATA'
const BLOCK_NUMBER_CHECKED = 'BLOCK_NUMBER_CHECKED'
const RECEIPT = 'receipt'
@ -94,7 +94,11 @@ export default function Provider({ children }) {
}, [])
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() {
const [, { add }] = useTransactionsContext()
return useCallback(
response => {
(response, customData = {}) => {
if (!(networkId || networkId === 0)) {
throw Error(`Invalid networkId '${networkId}`)
}
@ -155,7 +159,7 @@ export function useTransactionAdder() {
if (!hash) {
throw Error('No transaction hash found.')
}
add(networkId, hash, response)
add(networkId, hash, { ...response, [CUSTOM_DATA]: customData })
},
[networkId, add]
)
@ -178,12 +182,7 @@ export function usePendingApproval(tokenAddress) {
return false
} else if (!allTransactions[hash][RESPONSE]) {
return false
} else if (allTransactions[hash][RESPONSE].to !== tokenAddress) {
return false
} else if (
allTransactions[hash][RESPONSE].data.substring(0, 10) !==
ethers.utils.id('approve(address,uint256)').substring(0, 10)
) {
} else if (allTransactions[hash][RESPONSE][CUSTOM_DATA].approval !== tokenAddress) {
return false
} else {
return true

@ -305,7 +305,7 @@ export default function AddLiquidity() {
<>
<div>
{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>
{t('youWillMint')} {b(amountFormatter(liquidityMinted, 18, 4))} {t('liquidityTokens')}
@ -405,7 +405,7 @@ export default function AddLiquidity() {
// parse input value
useEffect(() => {
if (isNewExchange === false && inputValue && marketRate && lastEditedField === INPUT) {
if (isNewExchange === false && inputValue && marketRate && lastEditedField === INPUT && decimals) {
try {
const parsedValue = ethers.utils.parseUnits(inputValue, 18)
@ -418,10 +418,12 @@ export default function AddLiquidity() {
const currencyAmount = marketRate
.mul(parsedValue)
.div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18)))
.div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18 - decimals)))
setOutputValueParsed(currencyAmount)
dispatchAddLiquidityState({
type: 'UPDATE_DEPENDENT_VALUE',
payload: { field: OUTPUT, value: amountFormatter(currencyAmount, 18, 4, false) }
payload: { field: OUTPUT, value: amountFormatter(currencyAmount, decimals, 4, false) }
})
return () => {
@ -437,13 +439,13 @@ export default function AddLiquidity() {
setOutputError(t('inputNotValid'))
}
}
}, [inputValue, isNewExchange, lastEditedField, marketRate, t])
}, [inputValue, isNewExchange, lastEditedField, marketRate, decimals, t])
// parse output value
useEffect(() => {
if (isNewExchange === false && outputValue && marketRateInverted && lastEditedField === OUTPUT) {
if (isNewExchange === false && outputValue && marketRateInverted && lastEditedField === OUTPUT && decimals) {
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)) {
throw Error()
@ -453,7 +455,8 @@ export default function AddLiquidity() {
const currencyAmount = marketRateInverted
.mul(parsedValue)
.div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18)))
.div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(decimals)))
setInputValueParsed(currencyAmount)
dispatchAddLiquidityState({
type: 'UPDATE_DEPENDENT_VALUE',
@ -473,7 +476,7 @@ export default function AddLiquidity() {
setInputError(t('inputNotValid'))
}
}
}, [outputValue, isNewExchange, lastEditedField, marketRateInverted, t])
}, [outputValue, isNewExchange, lastEditedField, marketRateInverted, decimals, t])
// input validation
useEffect(() => {
@ -485,14 +488,14 @@ export default function AddLiquidity() {
}
}
if (outputValueParsed && outputBalance) {
if (outputValueParsed.gt(outputBalance)) {
if (outputValueMax && outputBalance) {
if (outputValueMax.gt(outputBalance)) {
setOutputError(t('insufficientBalance'))
} else {
setOutputError(null)
}
}
}, [inputValueParsed, inputBalance, outputValueParsed, outputBalance, t])
}, [inputValueParsed, inputBalance, outputValueMax, outputBalance, t])
const allowance = useAddressAllowance(account, outputCurrency, exchangeAddress)
const [showUnlock, setShowUnlock] = useState(false)
@ -590,7 +593,7 @@ export default function AddLiquidity() {
</OversizedPanel>
{renderSummary()}
<Flex>
<Button disabled={!isValid} onClick={onAddLiquidity} fullWidth>
<Button disabled={!isValid} onClick={onAddLiquidity}>
{t('addLiquidity')}
</Button>
</Flex>

@ -142,7 +142,7 @@ function CreateExchange({ history, location }) {
<SummaryText>{errorMessage ? errorMessage : t('enterTokenCont')}</SummaryText>
</CreateExchangeWrapper>
<Flex>
<Button disabled={!isValid} onClick={createExchange} fullWidth>
<Button disabled={!isValid} onClick={createExchange}>
{t('createExchange')}
</Button>
</Flex>

@ -206,13 +206,13 @@ export default function RemoveLiquidity() {
: undefined
const ethWithdrawn =
ETHPer &&
valueParsed &&
ETHPer.mul(valueParsed).div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18)))
ETHPer && valueParsed
? ETHPer.mul(valueParsed).div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18)))
: undefined
const tokenWithdrawn =
tokenPer &&
valueParsed &&
tokenPer.mul(valueParsed).div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18)))
tokenPer && valueParsed
? tokenPer.mul(valueParsed).div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18)))
: undefined
const ethWithdrawnMin = ethWithdrawn ? calculateSlippageBounds(ethWithdrawn).minimum : undefined
const tokenWithdrawnMin = tokenWithdrawn ? calculateSlippageBounds(tokenWithdrawn).minimum : undefined
@ -348,17 +348,17 @@ export default function RemoveLiquidity() {
</OversizedPanel>
<CurrencyInputPanel
title={t('output')}
description={ethWithdrawn && tokenWithdrawn ? `(${t('estimated')})` : ''}
description={ethWithdrawnMin && tokenWithdrawnMin ? `(${t('estimated')})` : ''}
key="remove-liquidity-input"
renderInput={() =>
ethWithdrawn && tokenWithdrawn ? (
ethWithdrawnMin && tokenWithdrawnMin ? (
<RemoveLiquidityOutput>
<RemoveLiquidityOutputText>
{`${amountFormatter(ethWithdrawn, 18, 4, false)} ETH`}
{`${amountFormatter(ethWithdrawnMin, 18, 4, false)} ETH`}
</RemoveLiquidityOutputText>
<RemoveLiquidityOutputPlus> + </RemoveLiquidityOutputPlus>
<RemoveLiquidityOutputText>
{`${amountFormatter(tokenWithdrawn, decimals, Math.min(4, decimals))} ${symbol}`}
{`${amountFormatter(tokenWithdrawnMin, decimals, Math.min(4, decimals))} ${symbol}`}
</RemoveLiquidityOutputText>
</RemoveLiquidityOutput>
) : (
@ -406,7 +406,7 @@ export default function RemoveLiquidity() {
</OversizedPanel>
{renderSummary()}
<Flex>
<Button disabled={!isValid} onClick={onRemoveLiquidity} fullWidth>
<Button disabled={!isValid} onClick={onRemoveLiquidity}>
{t('removeLiquidity')}
</Button>
</Flex>

@ -486,7 +486,11 @@ export default function Swap() {
.sub(ethers.utils.bigNumberify(3).mul(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(15))))
: undefined
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
@ -529,6 +533,11 @@ export default function Swap() {
{t('orTransFail')}
</LastSummaryText>
<LastSummaryText>
{(slippageWarning || highSlippageWarning) && (
<span role="img" aria-label="warning">
</span>
)}
{t('priceChange')} {b(`${percentSlippageFormatted}%`)}.
</LastSummaryText>
</div>
@ -586,13 +595,21 @@ export default function Swap() {
isError = true
}
const slippageWarningText = highSlippageWarning
? t('highSlippageWarning')
: slippageWarning
? t('slippageWarning')
: ''
return (
<NewContextualInfo
openDetailsText={t('transactionDetails')}
closeDetailsText={t('hideDetails')}
contextualInfo={contextualInfo ? contextualInfo : slippageWarning ? t('slippageWarning') : ''}
contextualInfo={contextualInfo ? contextualInfo : slippageWarningText}
allowExpand={!!(inputCurrency && outputCurrency && inputValueParsed && outputValueParsed && recipient.address)}
isError={isError}
slippageWarning={slippageWarning && slippageWarningText}
highSlippageWarning={highSlippageWarning && slippageWarningText}
renderTransactionDetails={renderTransactionDetails}
/>
)
@ -755,8 +772,8 @@ export default function Swap() {
</OversizedPanel>
{renderSummary()}
<Flex>
<Button disabled={!isValid} onClick={onSwap} fullWidth>
{t('swap')}
<Button disabled={!isValid} onClick={onSwap} warning={highSlippageWarning}>
{highSlippageWarning ? t('sendAnyway') : t('send')}
</Button>
</Flex>
</>

@ -481,7 +481,11 @@ export default function Swap() {
.sub(ethers.utils.bigNumberify(3).mul(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(15))))
: undefined
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
@ -524,6 +528,11 @@ export default function Swap() {
{t('orTransFail')}
</LastSummaryText>
<LastSummaryText>
{(slippageWarning || highSlippageWarning) && (
<span role="img" aria-label="warning">
</span>
)}
{t('priceChange')} {b(`${percentSlippageFormatted}%`)}.
</LastSummaryText>
</div>
@ -577,13 +586,21 @@ export default function Swap() {
isError = true
}
const slippageWarningText = highSlippageWarning
? t('highSlippageWarning')
: slippageWarning
? t('slippageWarning')
: ''
return (
<NewContextualInfo
openDetailsText={t('transactionDetails')}
closeDetailsText={t('hideDetails')}
contextualInfo={contextualInfo ? contextualInfo : slippageWarning ? t('slippageWarning') : ''}
contextualInfo={contextualInfo ? contextualInfo : slippageWarningText}
allowExpand={!!(inputCurrency && outputCurrency && inputValueParsed && outputValueParsed)}
isError={isError}
slippageWarning={slippageWarning && slippageWarningText}
highSlippageWarning={highSlippageWarning && slippageWarningText}
renderTransactionDetails={renderTransactionDetails}
/>
)
@ -726,8 +743,8 @@ export default function Swap() {
</OversizedPanel>
{renderSummary()}
<Flex>
<Button disabled={!isValid} onClick={onSwap}>
{t('swap')}
<Button disabled={!isValid} onClick={onSwap} warning={highSlippageWarning}>
{highSlippageWarning ? t('swapAnyway') : t('swap')}
</Button>
</Flex>
</>

@ -1,7 +1,9 @@
import styled from 'styled-components'
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;
border-radius: 3rem;
cursor: pointer;
@ -9,18 +11,18 @@ export const Button = styled.button`
font-size: 1rem;
border: none;
outline: none;
background-color: ${({ theme }) => theme.royalBlue};
background-color: ${({ backgroundColor }) => backgroundColor};
color: ${({ theme }) => theme.white};
transition: background-color 125ms ease-in-out;
width: 100%;
:hover,
:focus {
background-color: ${({ theme }) => lighten(0.05, theme.royalBlue)};
background-color: ${({ backgroundColor }) => lighten(0.05, backgroundColor)};
}
:active {
background-color: ${({ theme }) => darken(0.05, theme.royalBlue)};
background-color: ${({ backgroundColor }) => darken(0.05, backgroundColor)};
}
:disabled {

@ -57,6 +57,8 @@ const theme = {
salmonRed: '#FF6871',
// orange
pizazzOrange: '#FF8F05',
// yellows
warningYellow: '#FFE270',
// pink
uniswapPink: '#DC6BE5',
connectedGreen: '#27AE60',

@ -6,6 +6,8 @@ import ERC20_ABI from '../constants/abis/erc20'
import ERC20_BYTES32_ABI from '../constants/abis/erc20_bytes32'
import { FACTORY_ADDRESSES } from '../constants'
import UncheckedJsonRpcSigner from './signer'
export const ERROR_CODES = ['TOKEN_NAME', 'TOKEN_SYMBOL', 'TOKEN_DECIMALS'].reduce(
(accumulator, currentValue, currentIndex) => {
accumulator[currentValue] = currentIndex
@ -93,7 +95,7 @@ export function calculateGasMargin(value, margin) {
// account is optional
export function getProviderOrSigner(library, account) {
return account ? library.getSigner(account) : library
return account ? new UncheckedJsonRpcSigner(library.getSigner(account)) : library
}
// account is optional

36
src/utils/signer.js Normal file

@ -0,0 +1,36 @@
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:
utf8 "^3.0.0"
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"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.27.tgz#e570b0da9d805ad65c83d81919abe02b2264c6bf"
dependencies: