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:
parent
6579e17f7f
commit
f2f960f6fa
@ -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 })
|
||||
})
|
||||
}}
|
||||
>
|
||||
|
@ -94,12 +94,8 @@ const StyledNavLink = styled(NavLink).attrs({
|
||||
|
||||
: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
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)
|
||||
}
|
||||
}
|
18
yarn.lock
18
yarn.lock
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user