Refactors (#772)

* Bunch of refactoring

* Add an integration test that gets as far as swapping

* Drop the nonce code that isn't doing anything right now

* Undo one of the accidental changes to the reducer
This commit is contained in:
Moody Salem 2020-05-14 15:56:40 -04:00 committed by GitHub
parent 60582de4d6
commit b1ffab1890
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 214 additions and 276 deletions

@ -3,7 +3,7 @@ import { TEST_ADDRESS_NEVER_USE } from '../support/commands'
describe('Landing Page', () => {
beforeEach(() => cy.visit('/'))
it('loads exchange page', () => {
cy.get('#exchangePage')
cy.get('#exchange-page')
})
it('redirects to url /swap', () => {
@ -11,12 +11,12 @@ describe('Landing Page', () => {
})
it('allows navigation to send', () => {
cy.get('#send-navLink').click()
cy.get('#send-nav-link').click()
cy.url().should('include', '/send')
})
it('allows navigation to pool', () => {
cy.get('#pool-navLink').click()
cy.get('#pool-nav-link').click()
cy.url().should('include', '/pool')
})

@ -1,18 +1,28 @@
describe('Swap', () => {
beforeEach(() => cy.visit('/swap'))
it('can enter an amount into input', () => {
cy.get('#swapInputField').type('0.001')
cy.get('#swap-currency-input .token-amount-input').type('0.001')
})
it('zero swap amount', () => {
cy.get('#swapInputField').type('0.0')
cy.get('#swap-currency-input .token-amount-input').type('0.0')
})
it('can enter an amount into output', () => {
cy.get('#swapOutputField').type('0.001')
cy.get('#swap-currency-output .token-amount-input').type('0.001')
})
it('zero output amount', () => {
cy.get('#swapOutputField').type('0.0')
cy.get('#swap-currency-output .token-amount-input').type('0.0')
})
it('can swap ETH for DAI', () => {
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.get('.token-item-0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735').click()
cy.get('#swap-currency-input .token-amount-input').type('0.001')
cy.get('#swap-currency-output .token-amount-input').should('not.equal', '')
cy.get('#exchange-show-advanced').click()
cy.get('#exchange-swap-button').click()
cy.get('#exchange-page-confirm-swap-or-send').should('contain', 'Confirm Swap')
})
})

@ -12,7 +12,7 @@ import { TYPE, Link } from '../../theme'
import { AutoColumn, ColumnCenter } from '../Column'
import { ButtonPrimary, ButtonDropwdown, ButtonDropwdownLight } from '../Button'
import { useToken } from '../../contexts/Tokens'
import { useToken } from '../../hooks/Tokens'
import { useWeb3React } from '../../hooks'
import { usePair } from '../../data/Reserves'
@ -79,7 +79,7 @@ function CreatePool({ history }: RouteComponentProps<{}>) {
{token0?.symbol}{' '}
</Text>
<TYPE.darkGray fontWeight={500} fontSize={16} marginLeft={'8px'}>
{token0?.symbol === 'ETH' && '(default)'}
{token0?.address === 'ETH' && '(default)'}
</TYPE.darkGray>
</Row>
</ButtonDropwdownLight>

@ -136,7 +136,7 @@ interface CurrencyInputPanelProps {
showSendWithSwap?: boolean
otherSelectedTokenAddress?: string | null
advanced?: boolean
inputId: string
id: string
}
export default function CurrencyInputPanel({
@ -157,7 +157,7 @@ export default function CurrencyInputPanel({
showSendWithSwap = false,
otherSelectedTokenAddress = null,
advanced = false,
inputId
id
}: CurrencyInputPanelProps) {
const { t } = useTranslation()
@ -167,7 +167,7 @@ export default function CurrencyInputPanel({
const theme = useContext(ThemeContext)
return (
<InputPanel>
<InputPanel id={id}>
<Container hideInput={hideInput}>
{!hideInput && (
<LabelRow>
@ -197,8 +197,8 @@ export default function CurrencyInputPanel({
{!hideInput && (
<>
<NumericalInput
className="token-amount-input"
value={value}
id={inputId}
onUserInput={val => {
onUserInput(field, val)
}}
@ -210,6 +210,7 @@ export default function CurrencyInputPanel({
)}
<CurrencySelect
selected={!!token}
className="open-currency-select-button"
onClick={() => {
if (!disableTokenSelect) {
setModalOpen(true)
@ -223,7 +224,7 @@ export default function CurrencyInputPanel({
<TokenLogo address={token?.address} size={'24px'} />
) : null}
{isExchange ? (
<StyledTokenName>
<StyledTokenName className="token-name-container">
{pair?.token0.symbol}:{pair?.token1.symbol}
</StyledTokenName>
) : (

@ -1,37 +1,34 @@
import React, { useState, useCallback, useEffect, useContext } from 'react'
import ReactGA from 'react-ga'
import { ThemeContext } from 'styled-components'
import { parseEther, parseUnits } from '@ethersproject/units'
import { JSBI, Percent, TokenAmount, TradeType, WETH, Fraction } from '@uniswap/sdk'
import { ArrowDown, ChevronDown, ChevronUp, Repeat } from 'react-feather'
import { withRouter, RouteComponentProps } from 'react-router-dom'
import { BigNumber } from '@ethersproject/bignumber'
import { MaxUint256 } from '@ethersproject/constants'
import { Contract } from '@ethersproject/contracts'
import { useUserAdvanced } from '../../state/application/hooks'
import { useTokenBalanceTreatingWETHasETH, useAllTokenBalancesTreatingWETHasETH } from '../../state/wallet/hooks'
import { Field, SwapAction, useSwapStateReducer } from './swap-store'
import { parseEther, parseUnits } from '@ethersproject/units'
import { Fraction, JSBI, Percent, TokenAmount, TradeType, WETH } from '@uniswap/sdk'
import React, { useCallback, useContext, useEffect, useState } from 'react'
import { ArrowDown, ChevronDown, ChevronUp, Repeat } from 'react-feather'
import ReactGA from 'react-ga'
import { RouteComponentProps, withRouter } from 'react-router-dom'
import { Text } from 'rebass'
import { ThemeContext } from 'styled-components'
import Card, { BlueCard, GreyCard, YellowCard } from '../../components/Card'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import { AutoRow, RowBetween, RowFixed } from '../Row'
import { ROUTER_ADDRESS } from '../../constants'
import { useTokenAllowance } from '../../data/Allowances'
import { useAddUserToken, useFetchTokenByAddress } from '../../state/user/hooks'
import { useAllTokens, useToken } from '../../contexts/Tokens'
import { useHasPendingApproval, useTransactionAdder } from '../../state/transactions/hooks'
import { useV1TradeLinkIfBetter } from '../../data/V1'
import { useTokenContract, useWeb3React } from '../../hooks'
import { useTokenByAddressAndAutomaticallyAdd } from '../../hooks/Tokens'
import { useTradeExactIn, useTradeExactOut } from '../../hooks/Trades'
import { useWalletModalToggle } from '../../state/application/hooks'
import { useUserAdvanced, useWalletModalToggle } from '../../state/application/hooks'
import { useHasPendingApproval, useTransactionAdder } from '../../state/transactions/hooks'
import { useAllTokenBalancesTreatingWETHasETH } from '../../state/wallet/hooks'
import { Hover, TYPE } from '../../theme'
import { Link } from '../../theme/components'
import {
basisPointsToPercent,
calculateGasMargin,
getEtherscanLink,
getRouterContract,
basisPointsToPercent,
QueryParams,
getSigner
getSigner,
QueryParams
} from '../../utils'
import Copy from '../AccountDetails/Copy'
import AddressInputPanel from '../AddressInputPanel'
@ -39,6 +36,7 @@ import { ButtonError, ButtonLight, ButtonPrimary, ButtonSecondary } from '../But
import ConfirmationModal from '../ConfirmationModal'
import CurrencyInputPanel from '../CurrencyInputPanel'
import QuestionHelper from '../Question'
import { AutoRow, RowBetween, RowFixed } from '../Row'
import SlippageTabs from '../SlippageTabs'
import TokenLogo from '../TokenLogo'
import {
@ -55,7 +53,7 @@ import {
TruncatedText,
Wrapper
} from './styleds'
import { useV1TradeLinkIfBetter } from '../../data/V1'
import { Field, SwapAction, useSwapStateReducer } from './swap-store'
enum SwapType {
EXACT_TOKENS_FOR_TOKENS,
@ -108,35 +106,10 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
const [tradeError, setTradeError] = useState<string>('') // error for things like reserve size or route
const tokens = {
[Field.INPUT]: useToken(fieldData[Field.INPUT].address),
[Field.OUTPUT]: useToken(fieldData[Field.OUTPUT].address)
[Field.INPUT]: useTokenByAddressAndAutomaticallyAdd(fieldData[Field.INPUT].address),
[Field.OUTPUT]: useTokenByAddressAndAutomaticallyAdd(fieldData[Field.OUTPUT].address)
}
// ensure input + output tokens are added to localstorage
const fetchTokenByAddress = useFetchTokenByAddress()
const addToken = useAddUserToken()
const allTokens = useAllTokens()
const inputTokenAddress = fieldData[Field.INPUT].address
useEffect(() => {
if (inputTokenAddress && !Object.keys(allTokens).some(tokenAddress => tokenAddress === inputTokenAddress)) {
fetchTokenByAddress(inputTokenAddress).then(token => {
if (token !== null) {
addToken(token)
}
})
}
}, [inputTokenAddress, allTokens, fetchTokenByAddress, addToken])
const outputTokenAddress = fieldData[Field.OUTPUT].address
useEffect(() => {
if (outputTokenAddress && !Object.keys(allTokens).some(tokenAddress => tokenAddress === outputTokenAddress)) {
fetchTokenByAddress(outputTokenAddress).then(token => {
if (token !== null) {
addToken(token)
}
})
}
}, [outputTokenAddress, allTokens, fetchTokenByAddress, addToken])
// token contracts for approvals and direct sends
const tokenContractInput: Contract = useTokenContract(tokens[Field.INPUT]?.address)
const tokenContractOutput: Contract = useTokenContract(tokens[Field.OUTPUT]?.address)
@ -157,8 +130,8 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
// get user- and token-specific lookup data
const userBalances = {
[Field.INPUT]: useTokenBalanceTreatingWETHasETH(account, tokens[Field.INPUT]),
[Field.OUTPUT]: useTokenBalanceTreatingWETHasETH(account, tokens[Field.OUTPUT])
[Field.INPUT]: allBalances?.[tokens[Field.INPUT]?.address]?.raw,
[Field.OUTPUT]: allBalances?.[tokens[Field.OUTPUT]?.address]?.raw
}
// parse the amount that the user typed
@ -773,7 +746,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
if (sending && !sendingWithSwap) {
return (
<AutoColumn>
<ButtonPrimary onClick={onSend}>
<ButtonPrimary onClick={onSend} id="exchange-page-confirm-send">
<Text color="white" fontSize={20}>
Confirm send
</Text>
@ -868,7 +841,12 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
</AutoColumn>
<AutoRow>
<ButtonError onClick={onSwap} error={!!warningHigh} style={{ margin: '10px 0 0 0' }}>
<ButtonError
onClick={onSwap}
error={!!warningHigh}
style={{ margin: '10px 0 0 0' }}
id="exchange-page-confirm-swap-or-send"
>
<Text fontSize={20} fontWeight={500}>
{warningHigh ? (sending ? 'Send Anyway' : 'Swap Anyway') : sending ? 'Confirm Send' : 'Confirm Swap'}
</Text>
@ -951,7 +929,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
}
return (
<Wrapper id="exchangePage">
<Wrapper id="exchange-page">
<ConfirmationModal
isOpen={showConfirm}
title={sendingWithSwap ? 'Confirm swap and send' : sending ? 'Confirm Send' : 'Confirm Swap'}
@ -989,7 +967,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
showSendWithSwap={true}
advanced={advanced}
label={''}
inputId="swapInputField"
id="swap-currency-input"
otherSelectedTokenAddress={tokens[Field.OUTPUT]?.address}
/>
</InputGroup>
@ -1034,7 +1012,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
}}
onTokenSelection={address => onTokenSelection(Field.INPUT, address)}
otherSelectedTokenAddress={tokens[Field.OUTPUT]?.address}
inputId="swapInputField"
id="swap-currency-input"
/>
{sendingWithSwap ? (
@ -1078,7 +1056,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
onTokenSelection={address => onTokenSelection(Field.OUTPUT, address)}
advanced={advanced}
otherSelectedTokenAddress={tokens[Field.INPUT]?.address}
inputId="swapOutputField"
id="swap-currency-output"
/>
{sendingWithSwap && (
<RowBetween padding="0 1rem 0 12px">
@ -1199,6 +1177,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
onClick={() => {
setShowConfirm(true)
}}
id="exchange-swap-button"
disabled={!isValid}
error={!!warningHigh}
>
@ -1229,7 +1208,7 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
<AdvancedDropwdown>
{!showAdvanced && (
<Hover>
<RowBetween onClick={() => setShowAdvanced(true)} padding={'8px 20px'}>
<RowBetween onClick={() => setShowAdvanced(true)} padding={'8px 20px'} id="exchange-show-advanced">
<Text fontSize={16} fontWeight={500} style={{ userSelect: 'none' }}>
Show Advanced
</Text>

@ -142,7 +142,7 @@ function NavigationTabs({ location: { pathname }, history }: RouteComponentProps
<Tabs style={{ marginBottom: '20px' }}>
{tabOrder.map(({ path, textKey, regex }) => (
<StyledNavLink
id={`${textKey}-navLink`}
id={`${textKey}-nav-link`}
key={path}
to={path}
isActive={(_, { pathname }) => !!pathname.match(regex)}

@ -14,7 +14,7 @@ import { LightCard } from '../Card'
import { AutoColumn, ColumnCenter } from '../Column'
import { ButtonPrimary, ButtonDropwdown, ButtonDropwdownLight } from '../Button'
import { useToken } from '../../contexts/Tokens'
import { useToken } from '../../hooks/Tokens'
import { useWeb3React } from '@web3-react/core'
import { usePairAdder } from '../../state/user/hooks'
import { usePair } from '../../data/Reserves'

@ -31,7 +31,7 @@ import {
useRemoveUserAddedToken
} from '../../state/user/hooks'
import { useTranslation } from 'react-i18next'
import { useToken, useAllTokens, ALL_TOKENS } from '../../contexts/Tokens'
import { useToken, useAllTokens, ALL_TOKENS } from '../../hooks/Tokens'
import QuestionHelper from '../Question'
const TokenModalInfo = styled.div`
@ -226,13 +226,12 @@ function SearchModal({
const tokenList = useMemo(() => {
return Object.keys(allTokens)
.sort((a, b): number => {
if (a && allTokens[a]?.equals(WETH[chainId])) return -1
if (b && allTokens[b]?.equals(WETH[chainId])) return 1
if (allTokens[a].symbol && allTokens[b].symbol) {
const aSymbol = allTokens[a].symbol.toLowerCase()
const bSymbol = allTokens[b].symbol.toLowerCase()
// pin ETH to top
if (aSymbol === 'ETH'.toLowerCase() || bSymbol === 'ETH'.toLowerCase()) {
return aSymbol === bSymbol ? 0 : aSymbol === 'ETH'.toLowerCase() ? -1 : 1
}
// sort by balance
const balanceA = allBalances?.[account]?.[a]
const balanceB = allBalances?.[account]?.[b]
@ -256,7 +255,7 @@ function SearchModal({
balance: allBalances?.[account]?.[k]
}
})
}, [allTokens, allBalances, account, sortDirection])
}, [allTokens, allBalances, account, sortDirection, chainId])
const filteredTokenList = useMemo(() => {
return tokenList.filter(tokenEntry => {
@ -423,6 +422,7 @@ function SearchModal({
return (
<MenuItem
key={temporaryToken.address}
className={`temporary-token-${temporaryToken}`}
onClick={() => {
addToken(temporaryToken)
_onTokenSelect(temporaryToken.address)
@ -467,6 +467,7 @@ function SearchModal({
return (
<MenuItem
key={address}
className={`token-item-${address}`}
onClick={() => (hiddenToken && hiddenToken === address ? null : _onTokenSelect(address))}
disabled={hiddenToken && hiddenToken === address}
selected={otherSelectedTokenAddress === address}

@ -3,7 +3,7 @@ import React, { useState } from 'react'
import styled, { keyframes } from 'styled-components'
import { useWeb3React } from '../../hooks'
import { useToken } from '../../contexts/Tokens'
import { useToken } from '../../hooks/Tokens'
import { getEtherscanLink } from '../../utils'
import { Link } from '../../theme'

@ -1,7 +1,7 @@
import { ChainId, Token, WETH } from '@uniswap/sdk'
import { useMemo } from 'react'
import { useWeb3React } from '../hooks'
import { useUserAddedTokens } from '../state/user/hooks'
import { useEffect, useMemo } from 'react'
import { useAddUserToken, useFetchTokenByAddress, useUserAddedTokens } from '../state/user/hooks'
import { useWeb3React } from './index'
export const ALL_TOKENS = [
// WETH on all chains
@ -66,3 +66,23 @@ export function useToken(tokenAddress: string): Token {
return tokens?.[tokenAddress]
}
// gets token information by address (typically user input) and
// automatically adds it for the user if the token address is valid
export function useTokenByAddressAndAutomaticallyAdd(tokenAddress?: string): Token | undefined {
const fetchTokenByAddress = useFetchTokenByAddress()
const addToken = useAddUserToken()
const allTokens = useAllTokens()
useEffect(() => {
if (tokenAddress && !allTokens?.[tokenAddress]) {
fetchTokenByAddress(tokenAddress).then(token => {
if (token !== null) {
addToken(token)
}
})
}
}, [tokenAddress, allTokens, fetchTokenByAddress, addToken])
return useMemo(() => allTokens?.[tokenAddress], [allTokens, tokenAddress])
}

@ -1,39 +1,38 @@
import React, { useReducer, useState, useCallback, useEffect, useContext } from 'react'
import ReactGA from 'react-ga'
import styled, { ThemeContext } from 'styled-components'
import { RouteComponentProps, withRouter } from 'react-router-dom'
import { parseUnits, parseEther } from '@ethersproject/units'
import { BigNumber } from '@ethersproject/bignumber'
import { MaxUint256 } from '@ethersproject/constants'
import { Contract } from '@ethersproject/contracts'
import { WETH, TokenAmount, JSBI, Percent, Route, Token, Price } from '@uniswap/sdk'
import TokenLogo from '../../components/TokenLogo'
import DoubleLogo from '../../components/DoubleLogo'
import SearchModal from '../../components/SearchModal'
import PositionCard from '../../components/PositionCard'
import ConfirmationModal from '../../components/ConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import { Text } from 'rebass'
import { useTokenBalanceTreatingWETHasETH } from '../../state/wallet/hooks'
import { TYPE } from '../../theme'
import { parseEther, parseUnits } from '@ethersproject/units'
import { JSBI, Percent, Price, Route, Token, TokenAmount, WETH } from '@uniswap/sdk'
import React, { useCallback, useContext, useEffect, useReducer, useState } from 'react'
import { Plus } from 'react-feather'
import ReactGA from 'react-ga'
import { RouteComponentProps, withRouter } from 'react-router-dom'
import { Text } from 'rebass'
import styled, { ThemeContext } from 'styled-components'
import { ButtonLight, ButtonPrimary } from '../../components/Button'
import { BlueCard, GreyCard, LightCard } from '../../components/Card'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import { ButtonPrimary, ButtonLight } from '../../components/Button'
import Row, { AutoRow, RowBetween, RowFlat, RowFixed } from '../../components/Row'
import ConfirmationModal from '../../components/ConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import DoubleLogo from '../../components/DoubleLogo'
import PositionCard from '../../components/PositionCard'
import Row, { AutoRow, RowBetween, RowFixed, RowFlat } from '../../components/Row'
import SearchModal from '../../components/SearchModal'
import { useToken } from '../../contexts/Tokens'
import { useTokenAllowance } from '../../data/Allowances'
import { useTotalSupply } from '../../data/TotalSupply'
import { useWeb3React, useTokenContract } from '../../hooks'
import { useTransactionAdder, useHasPendingApproval } from '../../state/transactions/hooks'
import TokenLogo from '../../components/TokenLogo'
import { ROUTER_ADDRESS } from '../../constants'
import { getRouterContract, calculateGasMargin, calculateSlippageAmount } from '../../utils'
import { BigNumber } from '@ethersproject/bignumber'
import { useTokenAllowance } from '../../data/Allowances'
import { usePair } from '../../data/Reserves'
import { useAddUserToken, useFetchTokenByAddress } from '../../state/user/hooks'
import { useAllTokens } from '../../contexts/Tokens'
import { useTotalSupply } from '../../data/TotalSupply'
import { useTokenContract, useWeb3React } from '../../hooks'
import { useTokenByAddressAndAutomaticallyAdd } from '../../hooks/Tokens'
import { useHasPendingApproval, useTransactionAdder } from '../../state/transactions/hooks'
import { useTokenBalanceTreatingWETHasETH } from '../../state/wallet/hooks'
import { TYPE } from '../../theme'
import { calculateGasMargin, calculateSlippageAmount, getRouterContract } from '../../utils'
import { Dots, Wrapper } from './styleds'
// denominated in bips
const ALLOWED_SLIPPAGE = 50
@ -41,38 +40,12 @@ const ALLOWED_SLIPPAGE = 50
// denominated in seconds
const DEADLINE_FROM_NOW = 60 * 20
const Wrapper = styled.div`
position: relative;
`
const FixedBottom = styled.div`
position: absolute;
margin-top: 2rem;
width: 100%;
`
// styles
const Dots = styled.span`
&::after {
display: inline-block;
animation: ellipsis 1.25s infinite;
content: '.';
width: 1em;
text-align: left;
}
@keyframes ellipsis {
0% {
content: '.';
}
33% {
content: '..';
}
66% {
content: '...';
}
}
`
enum Field {
INPUT,
OUTPUT
@ -161,7 +134,7 @@ function reducer(
}
}
interface AddLiquidityProps extends RouteComponentProps<{}> {
interface AddLiquidityProps extends RouteComponentProps {
token0: string
token1: string
}
@ -183,36 +156,10 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
// get basic SDK entities
const tokens: { [field: number]: Token } = {
[Field.INPUT]: useToken(fieldData[Field.INPUT].address),
[Field.OUTPUT]: useToken(fieldData[Field.OUTPUT].address)
[Field.INPUT]: useTokenByAddressAndAutomaticallyAdd(fieldData[Field.INPUT].address),
[Field.OUTPUT]: useTokenByAddressAndAutomaticallyAdd(fieldData[Field.OUTPUT].address)
}
// ensure input + output tokens are added to localstorage
const fetchTokenByAddress = useFetchTokenByAddress()
const addToken = useAddUserToken()
const allTokens = useAllTokens()
const inputTokenAddress = fieldData[Field.INPUT].address
useEffect(() => {
if (inputTokenAddress && !Object.keys(allTokens).some(tokenAddress => tokenAddress === inputTokenAddress)) {
fetchTokenByAddress(inputTokenAddress).then(token => {
if (token !== null) {
addToken(token)
}
})
}
}, [inputTokenAddress, allTokens, fetchTokenByAddress, addToken])
const outputTokenAddress = fieldData[Field.OUTPUT].address
useEffect(() => {
if (outputTokenAddress && !Object.keys(allTokens).some(tokenAddress => tokenAddress === outputTokenAddress)) {
fetchTokenByAddress(outputTokenAddress).then(token => {
if (token !== null) {
addToken(token)
}
})
}
}, [outputTokenAddress, allTokens, fetchTokenByAddress, addToken])
// token contracts for approvals and direct sends
const tokenContractInput: Contract = useTokenContract(tokens[Field.INPUT]?.address)
const tokenContractOutput: Contract = useTokenContract(tokens[Field.OUTPUT]?.address)
@ -754,7 +701,7 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
onTokenSelection={address => onTokenSelection(Field.INPUT, address)}
pair={pair}
label="Input"
inputId="addLiquidityInput"
id="add-liquidity-input"
/>
<ColumnCenter>
<Plus size="16" color={theme.text2} />
@ -770,7 +717,7 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
token={tokens[Field.OUTPUT]}
onTokenSelection={address => onTokenSelection(Field.OUTPUT, address)}
pair={pair}
inputId="addLiquidityOutput"
id="add-liquidity-output"
/>
{tokens[Field.OUTPUT] && tokens[Field.INPUT] && (
<>

@ -1,36 +1,34 @@
import React, { useReducer, useState, useCallback, useEffect, useContext } from 'react'
import ReactGA from 'react-ga'
import styled, { ThemeContext } from 'styled-components'
import { parseUnits } from '@ethersproject/units'
import { splitSignature } from '@ethersproject/bytes'
import { Contract } from '@ethersproject/contracts'
import { TokenAmount, JSBI, Route, WETH, Percent, Token } from '@uniswap/sdk'
import { parseUnits } from '@ethersproject/units'
import { JSBI, Percent, Route, Token, TokenAmount, WETH } from '@uniswap/sdk'
import React, { useCallback, useContext, useEffect, useReducer, useState } from 'react'
import { ArrowDown, Plus } from 'react-feather'
import ReactGA from 'react-ga'
import { Text } from 'rebass'
import styled, { ThemeContext } from 'styled-components'
import { ButtonConfirmed, ButtonPrimary } from '../../components/Button'
import { LightCard } from '../../components/Card'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import ConfirmationModal from '../../components/ConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import DoubleLogo from '../../components/DoubleLogo'
import PositionCard from '../../components/PositionCard'
import Row, { RowBetween, RowFixed } from '../../components/Row'
import Slider from '../../components/Slider'
import TokenLogo from '../../components/TokenLogo'
import DoubleLogo from '../../components/DoubleLogo'
import PositionCard from '../../components/PositionCard'
import ConfirmationModal from '../../components/ConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import { ROUTER_ADDRESS } from '../../constants'
import { usePair } from '../../data/Reserves'
import { useTotalSupply } from '../../data/TotalSupply'
import { usePairContract, useWeb3React } from '../../hooks'
import { useToken } from '../../hooks/Tokens'
import { useTransactionAdder } from '../../state/transactions/hooks'
import { useTokenBalance } from '../../state/wallet/hooks'
import { TYPE } from '../../theme'
import { Text } from 'rebass'
import { LightCard } from '../../components/Card'
import { ButtonPrimary } from '../../components/Button'
import { ButtonConfirmed } from '../../components/Button'
import { ArrowDown, Plus } from 'react-feather'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import Row, { RowBetween, RowFixed } from '../../components/Row'
import { useToken } from '../../contexts/Tokens'
import { useWeb3React } from '../../hooks'
import { usePairContract } from '../../hooks'
import { useTransactionAdder } from '../../state/transactions/hooks'
import { useTotalSupply } from '../../data/TotalSupply'
import { splitSignature } from '@ethersproject/bytes'
import { ROUTER_ADDRESS } from '../../constants'
import { getRouterContract, calculateGasMargin, calculateSlippageAmount } from '../../utils'
import { usePair } from '../../data/Reserves'
import { calculateGasMargin, calculateSlippageAmount, getRouterContract } from '../../utils'
import { ClickableText, FixedBottom, MaxButton, Wrapper } from './styleds'
// denominated in bips
const ALLOWED_SLIPPAGE = 50
@ -38,48 +36,6 @@ const ALLOWED_SLIPPAGE = 50
// denominated in seconds
const DEADLINE_FROM_NOW = 60 * 20
const Wrapper = styled.div`
position: relative;
`
const FixedBottom = styled.div`
position: absolute;
top: 100px;
width: 100%;
margin-bottom: 80px;
`
const ClickableText = styled(Text)`
:hover {
cursor: pointer;
}
color: ${({ theme }) => theme.primary1};
`
// const CustomNumericalInput = styled(NumericalInput)`
// font-size: 72px;
// font-weight: 500;
// `
const MaxButton = styled.button<{ width: string }>`
padding: 0.5rem 1rem;
background-color: ${({ theme }) => theme.primary5};
border: 1px solid ${({ theme }) => theme.primary5};
border-radius: 0.5rem;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
margin-right: 0.5rem;
color: ${({ theme }) => theme.primary1};
:hover {
border: 1px solid ${({ theme }) => theme.primary1};
}
:focus {
border: 1px solid ${({ theme }) => theme.primary1};
outline: none;
}
`
enum Field {
LIQUIDITY,
TOKEN0,
@ -718,7 +674,7 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to
token={pair?.liquidityToken}
isExchange={true}
pair={pair}
inputId="liquidityAmount"
id="liquidity-amount"
/>
<ColumnCenter>
<ArrowDown size="16" color={theme.text2} />
@ -732,7 +688,7 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to
token={tokens[Field.TOKEN0]}
label={'Output'}
disableTokenSelect
inputId="removeLiquidityToken0"
id="remove-liquidity-token0"
/>
<ColumnCenter>
<Plus size="16" color={theme.text2} />
@ -746,7 +702,7 @@ export default function RemoveLiquidity({ token0, token1 }: { token0: string; to
token={tokens[Field.TOKEN1]}
label={'Output'}
disableTokenSelect
inputId="removeLiquidityToken1"
id="remove-liquidity-token1"
/>
</>
)}

@ -0,0 +1,57 @@
import { Text } from 'rebass'
import styled from 'styled-components'
export const Wrapper = styled.div`
position: relative;
`
export const FixedBottom = styled.div`
position: absolute;
top: 100px;
width: 100%;
margin-bottom: 80px;
`
export const ClickableText = styled(Text)`
:hover {
cursor: pointer;
}
color: ${({ theme }) => theme.primary1};
`
export const MaxButton = styled.button<{ width: string }>`
padding: 0.5rem 1rem;
background-color: ${({ theme }) => theme.primary5};
border: 1px solid ${({ theme }) => theme.primary5};
border-radius: 0.5rem;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
margin-right: 0.5rem;
color: ${({ theme }) => theme.primary1};
:hover {
border: 1px solid ${({ theme }) => theme.primary1};
}
:focus {
border: 1px solid ${({ theme }) => theme.primary1};
outline: none;
}
`
export const Dots = styled.span`
&::after {
display: inline-block;
animation: ellipsis 1.25s infinite;
content: '.';
width: 1em;
text-align: left;
}
@keyframes ellipsis {
0% {
content: '.';
}
33% {
content: '..';
}
66% {
content: '...';
}
}
`

@ -24,7 +24,3 @@ export const finalizeTransaction = createAction<{
hash: string
receipt: SerializableTransactionReceipt
}>('finalizeTransaction')
export const updateTransactionCount = createAction<{ address: string; transactionCount: number; chainId: number }>(
'updateTransactionCount'
)

@ -1,12 +1,5 @@
import { createReducer } from '@reduxjs/toolkit'
import { isAddress } from '../../utils'
import {
addTransaction,
checkTransaction,
finalizeTransaction,
SerializableTransactionReceipt,
updateTransactionCount
} from './actions'
import { addTransaction, checkTransaction, finalizeTransaction, SerializableTransactionReceipt } from './actions'
const now = () => new Date().getTime()
@ -19,7 +12,6 @@ export interface TransactionDetails {
addedTime: number
confirmedTime?: number
from: string
nonce?: number // todo: find a way to populate this
// set to true when we receive a transaction count that exceeds the nonce of this transaction
unknownStatus?: boolean
@ -58,14 +50,4 @@ export default createReducer(initialState, builder =>
state[chainId][hash].unknownStatus = false
state[chainId][hash].confirmedTime = now()
})
// marks every transaction with a nonce less than the transaction count unknown if it was pending
// this can be overridden by a finalize that comes later
.addCase(updateTransactionCount, (state, { payload: { transactionCount, address, chainId } }) => {
// mark any transactions under the transaction count to be unknown status
Object.values(state?.[chainId] ?? {})
.filter(t => !t.receipt)
.filter(t => t.from === isAddress(address))
.filter(t => typeof t.nonce && t.nonce < transactionCount)
.forEach(t => (t.unknownStatus = t.unknownStatus ?? true))
})
)

@ -3,11 +3,10 @@ import { useDispatch, useSelector } from 'react-redux'
import { useWeb3React } from '../../hooks'
import { useAddPopup, useBlockNumber } from '../application/hooks'
import { AppDispatch, AppState } from '../index'
import { checkTransaction, finalizeTransaction, updateTransactionCount } from './actions'
import useSWR from 'swr'
import { checkTransaction, finalizeTransaction } from './actions'
export default function Updater() {
const { chainId, account, library } = useWeb3React()
const { chainId, library } = useWeb3React()
const lastBlockNumber = useBlockNumber()
@ -19,16 +18,6 @@ export default function Updater() {
// show popup on confirm
const addPopup = useAddPopup()
const { data: transactionCount } = useSWR<number | null>(['accountNonce', account, lastBlockNumber], () => {
if (!account) return null
return library.getTransactionCount(account, 'latest')
})
useEffect(() => {
if (transactionCount === null) return
dispatch(updateTransactionCount({ address: account, transactionCount, chainId }))
}, [transactionCount, account, chainId, dispatch])
useEffect(() => {
if (typeof chainId === 'number' && library) {
Object.keys(allTransactions)

@ -2,7 +2,7 @@ import { ChainId, JSBI, Pair, Token, TokenAmount, WETH } from '@uniswap/sdk'
import { useWeb3React } from '@web3-react/core'
import { useCallback, useMemo } from 'react'
import { shallowEqual, useDispatch, useSelector } from 'react-redux'
import { useAllTokens } from '../../contexts/Tokens'
import { useAllTokens } from '../../hooks/Tokens'
import { getTokenDecimals, getTokenName, getTokenSymbol } from '../../utils'
import { AppDispatch, AppState } from '../index'
import {

@ -2,7 +2,7 @@ import { getAddress } from '@ethersproject/address'
import { JSBI, Token, TokenAmount, WETH } from '@uniswap/sdk'
import { useEffect, useMemo } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { useAllTokens } from '../../contexts/Tokens'
import { useAllTokens } from '../../hooks/Tokens'
import { usePrevious, useWeb3React } from '../../hooks'
import { isAddress } from '../../utils'
import { AppDispatch, AppState } from '../index'