Commit 2ca3bf8d authored by ianlapham's avatar ianlapham

add slider to remove

parent 236c3030
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@ethersproject/units": "^5.0.0-beta.132", "@ethersproject/units": "^5.0.0-beta.132",
"@material-ui/core": "^4.9.5",
"@reach/dialog": "^0.2.8", "@reach/dialog": "^0.2.8",
"@reach/tooltip": "^0.2.0", "@reach/tooltip": "^0.2.0",
"@types/jest": "^25.1.3", "@types/jest": "^25.1.3",
......
import React from 'react' import React from 'react'
import { Button as RebassButton } from 'rebass/styled-components' import { Button as RebassButton } from 'rebass/styled-components'
import styled from 'styled-components' import styled from 'styled-components'
import { darken } from 'polished' import { darken, lighten } from 'polished'
import { RowBetween } from '../Row' import { RowBetween } from '../Row'
import { ChevronDown } from 'react-feather' import { ChevronDown } from 'react-feather'
...@@ -87,6 +87,24 @@ export const ButtonEmpty = styled(Base)` ...@@ -87,6 +87,24 @@ export const ButtonEmpty = styled(Base)`
} }
` `
const ButtonConfirmedStyle = styled(Base)`
background-color: ${({ theme }) => lighten(0.5, theme.connectedGreen)};
border: 1px solid ${({ theme }) => theme.connectedGreen};
&:disabled {
opacity: 50%;
cursor: auto;
}
`
export function ButtonConfirmed({ children, confirmed, ...rest }) {
if (confirmed) {
return <ButtonConfirmedStyle {...rest}>{children}</ButtonConfirmedStyle>
} else {
return <ButtonPrimary {...rest}>{children}</ButtonPrimary>
}
}
export function ButtonDropwdown({ disabled, children, ...rest }) { export function ButtonDropwdown({ disabled, children, ...rest }) {
return ( return (
<ButtonPrimary {...rest}> <ButtonPrimary {...rest}>
......
...@@ -8,9 +8,12 @@ const Card = styled(Box)` ...@@ -8,9 +8,12 @@ const Card = styled(Box)`
padding: ${({ padding }) => padding}; padding: ${({ padding }) => padding};
border: ${({ border }) => border}; border: ${({ border }) => border};
` `
export default Card export default Card
export const LightCard = styled(Card)` export const LightCard = styled(Card)`
border: 1px solid ${({ theme }) => theme.outlineGrey}; border: 1px solid ${({ theme }) => theme.outlineGrey};
` `
export const GreyCard = styled(Card)`
background-color: rgba(255, 255, 255, 0.6);
`
import React, { useState } from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { ButtonPrimary } from '../Button' import { ButtonPrimary } from '../Button'
...@@ -6,6 +6,7 @@ import { AutoColumn, ColumnCenter } from '../Column' ...@@ -6,6 +6,7 @@ import { AutoColumn, ColumnCenter } from '../Column'
import Row, { RowBetween, RowFlat, RowFixed } from '../Row' import Row, { RowBetween, RowFlat, RowFixed } from '../Row'
import { ArrowDown } from 'react-feather' import { ArrowDown } from 'react-feather'
import { ButtonConfirmed } from '../Button'
import { Text } from 'rebass' import { Text } from 'rebass'
import { LightCard } from '../Card' import { LightCard } from '../Card'
import Modal from '../Modal' import Modal from '../Modal'
...@@ -35,6 +36,10 @@ const ConfirmedIcon = styled(ColumnCenter)` ...@@ -35,6 +36,10 @@ const ConfirmedIcon = styled(ColumnCenter)`
padding: 60px 0; padding: 60px 0;
` `
const ConfirmedText = styled(Text)`
color: ${({ theme, confirmed }) => (confirmed ? theme.connectedGreen : theme.white)};
`
export default function ConfirmationModal({ export default function ConfirmationModal({
isOpen, isOpen,
onDismiss, onDismiss,
...@@ -46,23 +51,23 @@ export default function ConfirmationModal({ ...@@ -46,23 +51,23 @@ export default function ConfirmationModal({
transactionType, transactionType,
pendingConfirmation, pendingConfirmation,
hash, hash,
contractCall signed = false,
contractCall,
attemptedRemoval = false,
extraCall = undefined
}) { }) {
const { address: address0, symbol: symbol0 } = amount0?.token || {} const { address: address0, symbol: symbol0 } = amount0?.token || {}
const { address: address1, symbol: symbol1 } = amount1?.token || {} const { address: address1, symbol: symbol1 } = amount1?.token || {}
const { chainId } = useWeb3React() const { chainId } = useWeb3React()
const [confirmed, setConfirmed] = useState(false)
function WrappedOnDismissed() { function WrappedOnDismissed() {
onDismiss() onDismiss()
setConfirmed(false)
} }
return ( return (
<Modal isOpen={isOpen} onDismiss={onDismiss}> <Modal isOpen={isOpen} onDismiss={WrappedOnDismissed}>
{!confirmed ? ( {!attemptedRemoval ? (
<Wrapper> <Wrapper>
<Section gap="40px"> <Section gap="40px">
<RowBetween> <RowBetween>
...@@ -195,22 +200,51 @@ export default function ConfirmationModal({ ...@@ -195,22 +200,51 @@ export default function ConfirmationModal({
</Text> </Text>
</RowBetween> </RowBetween>
)} )}
<ButtonPrimary {transactionType === TRANSACTION_TYPE.REMOVE ? (
style={{ margin: '20px 0' }} <RowBetween gap="20px">
onClick={() => { <ButtonConfirmed
setConfirmed(true) style={{ margin: '20px 0' }}
contractCall() width="48%"
}} onClick={() => {
> extraCall()
<Text fontWeight={500} fontSize={20}> }}
Confirm{' '} confirmed={signed}
{transactionType === TRANSACTION_TYPE.ADD disabled={signed}
? 'Supply' >
: transactionType === TRANSACTION_TYPE.REMOVE <ConfirmedText fontWeight={500} fontSize={20} confirmed={signed}>
? 'Remove' {signed ? 'Signed' : 'Sign'}
: 'Swap'} </ConfirmedText>
</Text> </ButtonConfirmed>
</ButtonPrimary> <ButtonPrimary
width="48%"
disabled={!signed}
style={{ margin: '20px 0' }}
onClick={() => {
contractCall()
}}
>
<Text fontWeight={500} fontSize={20}>
Confirm Remove
</Text>
</ButtonPrimary>
</RowBetween>
) : (
<ButtonPrimary
style={{ margin: '20px 0' }}
onClick={() => {
contractCall()
}}
>
<Text fontWeight={500} fontSize={20}>
Confirm{' '}
{transactionType === TRANSACTION_TYPE.ADD
? 'Supply'
: transactionType === TRANSACTION_TYPE.REMOVE
? 'Remove'
: 'Swap'}
</Text>
</ButtonPrimary>
)}
{transactionType === TRANSACTION_TYPE.ADD && ( {transactionType === TRANSACTION_TYPE.ADD && (
<Text fontSize={12} color="#565A69" textAlign="center"> <Text fontSize={12} color="#565A69" textAlign="center">
{`Output is estimated. You will receive at least ${liquidityAmount?.toFixed( {`Output is estimated. You will receive at least ${liquidityAmount?.toFixed(
......
...@@ -245,7 +245,7 @@ export default function CurrencyInputPanel({ ...@@ -245,7 +245,7 @@ export default function CurrencyInputPanel({
<ErrorSpan data-tip={'Enter max'} error={!!error} onClick={() => {}}></ErrorSpan> <ErrorSpan data-tip={'Enter max'} error={!!error} onClick={() => {}}></ErrorSpan>
<ClickableText onClick={onMax}> <ClickableText onClick={onMax}>
<Text> <Text>
Balance: {customBalance ? customBalance.toSignificant(4) : userTokenBalance?.toSignificant(4)} Balance: {customBalance ? customBalance?.toSignificant(4) : userTokenBalance?.toSignificant(4)}
</Text> </Text>
</ClickableText> </ClickableText>
</RowBetween> </RowBetween>
...@@ -273,7 +273,7 @@ export default function CurrencyInputPanel({ ...@@ -273,7 +273,7 @@ export default function CurrencyInputPanel({
> >
<Aligner> <Aligner>
{isExchange ? ( {isExchange ? (
<DoubleLogo a0={exchange?.token0.address} a1={exchange?.token1.address} margin={true} /> <DoubleLogo a0={exchange?.token0.address} a1={exchange?.token1.address} size={24} margin={true} />
) : token?.address ? ( ) : token?.address ? (
<TokenLogo address={token?.address} size={'24px'} /> <TokenLogo address={token?.address} size={'24px'} />
) : null} ) : null}
......
...@@ -7,7 +7,7 @@ export default function DoubleTokenLogo({ a0, a1, size = 16, margin = false }) { ...@@ -7,7 +7,7 @@ export default function DoubleTokenLogo({ a0, a1, size = 16, margin = false }) {
position: relative; position: relative;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
margin-right: ${({ sizeraw, margin }) => margin && (sizeraw / 2 + 10).toString() + 'px'}; margin-right: ${({ sizeraw, margin }) => margin && (sizeraw / 3 + 8).toString() + 'px'};
` `
const HigherLogo = styled(TokenLogo)` const HigherLogo = styled(TokenLogo)`
......
import React, { useState, useReducer, useCallback, useEffect } from 'react' import React, { useState, useReducer, useCallback, useEffect } from 'react'
import { ethers } from 'ethers'
import styled from 'styled-components' import styled from 'styled-components'
import { ethers } from 'ethers'
import { parseUnits, parseEther } from '@ethersproject/units' import { parseUnits, parseEther } from '@ethersproject/units'
import { WETH, TradeType, Route, Trade, TokenAmount, JSBI } from '@uniswap/sdk' import { WETH, TradeType, Route, Trade, TokenAmount, JSBI } from '@uniswap/sdk'
import { useWeb3React } from '../../hooks'
import { useToken } from '../../contexts/Tokens' import { useToken } from '../../contexts/Tokens'
import { useExchange } from '../../contexts/Exchanges' import { useExchange } from '../../contexts/Exchanges'
import { useTransactionAdder } from '../../contexts/Transactions' import { useWeb3React } from '../../hooks'
import { useAddressBalance } from '../../contexts/Balances' import { useAddressBalance } from '../../contexts/Balances'
import { useTransactionAdder } from '../../contexts/Transactions'
import { useAddressAllowance } from '../../contexts/Allowances' import { useAddressAllowance } from '../../contexts/Allowances'
import ConfirmationModal from '../ConfirmationModal'
import CurrencyInputPanel from '../CurrencyInputPanel'
import { Text } from 'rebass' import { Text } from 'rebass'
import { ButtonPrimary } from '../Button'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import { RowBetween } from '../../components/Row' import { RowBetween } from '../../components/Row'
import { ButtonPrimary } from '../Button'
import { ArrowDown, ArrowUp } from 'react-feather' import { ArrowDown, ArrowUp } from 'react-feather'
import CurrencyInputPanel from '../CurrencyInputPanel' import { AutoColumn, ColumnCenter } from '../../components/Column'
import ConfirmationModal from '../ConfirmationModal'
import { TRANSACTION_TYPE, ROUTER_ADDRESSES } from '../../constants'
import { getRouterContract, calculateGasMargin } from '../../utils' import { getRouterContract, calculateGasMargin } from '../../utils'
import { TRANSACTION_TYPE } from '../../constants'
const ArrowWrapper = styled.div` const ArrowWrapper = styled.div`
padding: 4px; padding: 4px;
...@@ -138,6 +138,7 @@ function reducer( ...@@ -138,6 +138,7 @@ function reducer(
export default function ExchangePage() { export default function ExchangePage() {
const { chainId, account, library } = useWeb3React() const { chainId, account, library } = useWeb3React()
const routerAddress = ROUTER_ADDRESSES[chainId]
const [state, dispatch] = useReducer(reducer, WETH[chainId].address, initializeSwapState) const [state, dispatch] = useReducer(reducer, WETH[chainId].address, initializeSwapState)
const { independentField, typedValue, ...fieldData } = state const { independentField, typedValue, ...fieldData } = state
...@@ -326,8 +327,6 @@ export default function ExchangePage() { ...@@ -326,8 +327,6 @@ export default function ExchangePage() {
: calculateSlippageAmount(parsedAmounts[Field.OUTPUT])?.[0] : calculateSlippageAmount(parsedAmounts[Field.OUTPUT])?.[0]
} }
const routerAddress = '0xd9210Ff5A0780E083BB40e30d005d93a2DcFA4EF'
const inputApproval = useAddressAllowance(account, tokens[Field.INPUT], routerAddress) const inputApproval = useAddressAllowance(account, tokens[Field.INPUT], routerAddress)
const outputApproval = useAddressAllowance(account, tokens[Field.OUTPUT], routerAddress) const outputApproval = useAddressAllowance(account, tokens[Field.OUTPUT], routerAddress)
......
import React from 'react'
import styled from 'styled-components'
import { withRouter } from 'react-router-dom'
import { Percent, Exchange } from '@uniswap/sdk'
import { useWeb3React } from '@web3-react/core'
import { useAllBalances } from '../../contexts/Balances'
import { useTotalSupply } from '../../contexts/Exchanges'
import Card from '../Card'
import TokenLogo from '../TokenLogo'
import DoubleLogo from '../DoubleLogo'
import { Text } from 'rebass'
import { GreyCard } from '../../components/Card'
import { AutoColumn } from '../Column'
import { ButtonSecondary } from '../Button'
import { RowBetween, RowFixed } from '../Row'
const FixedHeightRow = styled(RowBetween)`
height: 24px;
`
function PositionCard({ exchangeAddress, token0, token1, history, minimal = false, ...rest }) {
const { account } = useWeb3React()
const allBalances = useAllBalances()
const tokenAmount0 = allBalances?.[exchangeAddress]?.[token0.address]
const tokenAmount1 = allBalances?.[exchangeAddress]?.[token1.address]
const exchange = tokenAmount0 && tokenAmount1 && new Exchange(tokenAmount0, tokenAmount1)
const userPoolBalance = allBalances?.[account]?.[exchangeAddress]
const totalPoolTokens = useTotalSupply(exchange)
const poolTokenPercentage =
!!userPoolBalance && !!totalPoolTokens ? new Percent(userPoolBalance.raw, totalPoolTokens.raw) : undefined
const token0Deposited = poolTokenPercentage?.multiply(allBalances[exchangeAddress][token0.address])
const token1Deposited = poolTokenPercentage?.multiply(allBalances[exchangeAddress][token1.address])
function DynamicCard({ children, ...rest }) {
if (!minimal) {
return (
<Card border="1px solid #EDEEF2" {...rest}>
{children}
</Card>
)
} else {
return <GreyCard {...rest}>{children}</GreyCard>
}
}
return (
<DynamicCard {...rest}>
<AutoColumn gap="20px">
<FixedHeightRow>
<Text fontWeight={500} fontSize={16}>
Current Position
</Text>
</FixedHeightRow>
<FixedHeightRow>
<RowFixed>
<DoubleLogo a0={token0?.address || ''} a1={token1?.address || ''} margin={true} size={24} />
<Text fontWeight={500} fontSize={20}>
{token0?.symbol}:{token1?.symbol}
</Text>
</RowFixed>
<Text fontWeight={500} fontSize={20}>
{userPoolBalance ? userPoolBalance.toFixed(6) : '-'}
</Text>
</FixedHeightRow>
<AutoColumn gap="12px">
<FixedHeightRow>
<Text color="#888D9B" fontSize={16} fontWeight={500}>
{token0?.symbol} Deposited:
</Text>
{token0Deposited ? (
<RowFixed>
<TokenLogo address={token0?.address || ''} />
<Text color="#888D9B" fontSize={16} fontWeight={500} marginLeft={'6px'}>
{token0Deposited?.toFixed(8)}
</Text>
</RowFixed>
) : (
'-'
)}
</FixedHeightRow>
<FixedHeightRow>
<Text color="#888D9B" fontSize={16} fontWeight={500}>
{token1?.symbol} Deposited:
</Text>
{token1Deposited ? (
<RowFixed>
<TokenLogo address={token1.address || ''} />
<Text color="#888D9B" fontSize={16} fontWeight={500} marginLeft={'6px'}>
{token1Deposited?.toFixed(8)}
</Text>
</RowFixed>
) : (
'-'
)}
</FixedHeightRow>
{!minimal && (
<FixedHeightRow>
<Text color="#888D9B" fontSize={16} fontWeight={500}>
Your pool share:
</Text>
<Text color="#888D9B" fontSize={16} fontWeight={500}>
{poolTokenPercentage ? poolTokenPercentage.toFixed(2) + '%' : '-'}
</Text>
</FixedHeightRow>
)}
</AutoColumn>
{!minimal && (
<RowBetween>
<ButtonSecondary
width="48%"
onClick={() => {
history.push('/add/' + token0?.address + '-' + token1?.address)
}}
>
Add
</ButtonSecondary>
<ButtonSecondary
width="48%"
onClick={() => {
history.push('/remove/' + token0?.address + '-' + token1?.address)
}}
>
Remove
</ButtonSecondary>
</RowBetween>
)}
</AutoColumn>
</DynamicCard>
)
}
export default withRouter(PositionCard)
...@@ -201,8 +201,8 @@ function SearchModal({ history, isOpen, onDismiss, onTokenSelect, field, urlAdde ...@@ -201,8 +201,8 @@ function SearchModal({ history, isOpen, onDismiss, onTokenSelect, field, urlAdde
} }
// amount of tokens to display at once // amount of tokens to display at once
const [tokensShown, setTokensShown] = useState(0) const [, setTokensShown] = useState(0)
const [pairsShown, setPairsShown] = useState(0) const [, setPairsShown] = useState(0)
// filters on results // filters on results
const FILTERS = { const FILTERS = {
......
import React from 'react'
import Slider from '@material-ui/core/Slider'
import { withStyles } from '@material-ui/core/styles'
const IOSSlider = withStyles({
root: {
width: '95%',
color: '#3880ff',
height: 4,
padding: '15px 0'
},
thumb: {
height: 28,
width: 28,
backgroundColor: '##2172E5',
marginTop: -14,
marginLeft: -14,
'&:focus,&:hover,&$active': {
boxShadow:
'0px 0px 1px rgba(0, 0, 0, 0.04), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04), 0px 24px 32px rgba(0, 0, 0, 0.04)',
// Reset on touch devices, it doesn't add specificity
'@media (hover: none)': {}
}
},
active: {},
track: {
height: 4
},
rail: {
height: 2,
opacity: 0.5,
backgroundColor: '#bfbfbf'
},
mark: {
backgroundColor: '#bfbfbf',
height: 8,
width: 1,
marginTop: -3
},
markActive: {
opacity: 1,
backgroundColor: 'currentColor'
}
})(Slider)
export default function InputSlider({ value, onChange }) {
return <IOSSlider value={typeof value === 'number' ? value : 0} onChange={onChange} aria-labelledby="input-slider" />
}
...@@ -26,8 +26,7 @@ const Emoji = styled.span` ...@@ -26,8 +26,7 @@ const Emoji = styled.span`
font-size: ${({ size }) => size}; font-size: ${({ size }) => size};
width: ${({ size }) => size}; width: ${({ size }) => size};
height: ${({ size }) => size}; height: ${({ size }) => size};
margin-bottom: -2px; margin-bottom: -4px;
margin-right: ${({ sizeraw, margin }) => margin && (sizeraw / 2 + 10).toString() + 'px'};
` `
const StyledEthereumLogo = styled(EthereumLogo)` const StyledEthereumLogo = styled(EthereumLogo)`
...@@ -35,7 +34,7 @@ const StyledEthereumLogo = styled(EthereumLogo)` ...@@ -35,7 +34,7 @@ const StyledEthereumLogo = styled(EthereumLogo)`
height: ${({ size }) => size}; height: ${({ size }) => size};
` `
export default function TokenLogo({ address, size = '1rem', ...rest }) { export default function TokenLogo({ address, size = '24px', ...rest }) {
const [error, setError] = useState(false) const [error, setError] = useState(false)
const { chainId } = useWeb3React() const { chainId } = useWeb3React()
......
import React, { createContext, useContext, useReducer, useRef, useMemo, useCallback, useEffect, ReactNode } from 'react' import React, { createContext, useContext, useReducer, useRef, useMemo, useCallback, useEffect, ReactNode } from 'react'
import { TokenAmount, Token, JSBI, WETH } from '@uniswap/sdk' import { TokenAmount, Token, JSBI, WETH } from '@uniswap/sdk'
import { useWeb3React, useDebounce } from '../hooks'
import { getEtherBalance, getTokenBalance, isAddress } from '../utils'
import { useBlockNumber } from './Application'
import { useAllTokens } from './Tokens' import { useAllTokens } from './Tokens'
import { useBlockNumber } from './Application'
import { useAllExchanges } from './Exchanges' import { useAllExchanges } from './Exchanges'
import { useWeb3React, useDebounce } from '../hooks'
import { getEtherBalance, getTokenBalance, isAddress } from '../utils'
const LOCAL_STORAGE_KEY = 'BALANCES' const LOCAL_STORAGE_KEY = 'BALANCES'
const SHORT_BLOCK_TIMEOUT = (60 * 2) / 15 // in seconds, represented as a block number delta const SHORT_BLOCK_TIMEOUT = (60 * 2) / 15 // in seconds, represented as a block number delta
...@@ -408,30 +409,23 @@ export function useAllBalances(): Array<TokenAmount> { ...@@ -408,30 +409,23 @@ export function useAllBalances(): Array<TokenAmount> {
const allTokens = useAllTokens() const allTokens = useAllTokens()
const formattedBalances = useMemo(() => { const formattedBalances = useMemo(() => {
if (!state?.[chainId]) { if (!state || !state[chainId]) {
return {} return {}
} else { } else {
let newBalances = {} let newBalances = {}
Object.keys(state[chainId]).map(address => { Object.keys(state[chainId]).map(address => {
return Object.keys(state[chainId][address]).map(tokenAddress => { return Object.keys(state[chainId][address]).map(tokenAddress => {
if (state?.[chainId][address][tokenAddress].value) { if (state[chainId][address][tokenAddress].value) {
return (newBalances[chainId] = { newBalances[chainId] = {
...newBalances[chainId], ...newBalances[chainId],
[address]: { [address]: {
...newBalances[chainId]?.[address], ...newBalances[chainId]?.[address],
[tokenAddress]: new TokenAmount( [tokenAddress]: new TokenAmount(
// if token not in token list, must be an exchange - allTokens[tokenAddress] ? allTokens[tokenAddress] : new Token(chainId, tokenAddress, 18),
/**
* @TODO
*
* should we live fetch data here if token not in list
*
*/
allTokens && allTokens[tokenAddress] ? allTokens[tokenAddress] : new Token(chainId, tokenAddress, 18),
JSBI.BigInt(state?.[chainId][address][tokenAddress].value) JSBI.BigInt(state?.[chainId][address][tokenAddress].value)
) )
} }
}) }
} }
}) })
}) })
...@@ -485,7 +479,3 @@ export function useAccountLPBalances(account: string) { ...@@ -485,7 +479,3 @@ export function useAccountLPBalances(account: string) {
}) })
}, [account, allExchanges, chainId, startListening, stopListening]) }, [account, allExchanges, chainId, startListening, stopListening])
} }
export function useExchangeReserves(tokenAddress: string) {
return []
}
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react' import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect, useState } from 'react'
import { ChainId, WETH, Token, Exchange } from '@uniswap/sdk'
import { INITIAL_TOKENS_CONTEXT } from './Tokens'
import { useAddressBalance } from './Balances' import { useAddressBalance } from './Balances'
import { useWeb3React, useExchangeContract } from '../hooks'
import { useWeb3React } from '../hooks' import { INITIAL_TOKENS_CONTEXT } from './Tokens'
import { ChainId, WETH, Token, TokenAmount, Exchange, JSBI } from '@uniswap/sdk'
const UPDATE = 'UPDATE' const UPDATE = 'UPDATE'
...@@ -138,3 +139,46 @@ export function useAllExchanges() { ...@@ -138,3 +139,46 @@ export function useAllExchanges() {
return allExchanges || {} return allExchanges || {}
}, [allExchanges]) }, [allExchanges])
} }
export function useTotalSupply(exchange: Exchange) {
const { library } = useWeb3React()
const [totalPoolTokens, setTotalPoolTokens] = useState<TokenAmount>()
const exchangeContract = useExchangeContract(exchange?.liquidityToken.address)
const fetchPoolTokens = useCallback(async () => {
!!exchangeContract &&
exchangeContract
.deployed()
.then(() => {
if (exchangeContract) {
exchangeContract.totalSupply().then(totalSupply => {
if (totalSupply !== undefined && exchange?.liquidityToken?.decimals) {
const supplyFormatted = JSBI.BigInt(totalSupply)
const tokenSupplyFormatted = new TokenAmount(exchange?.liquidityToken, supplyFormatted)
setTotalPoolTokens(tokenSupplyFormatted)
}
})
}
})
.catch(e => {
console.log('error')
})
/**
* @todo
* fix this
*/
}, [exchangeContract])
// on the block make sure we're updated
useEffect(() => {
fetchPoolTokens()
library.on('block', fetchPoolTokens)
return () => {
library.removeListener('block', fetchPoolTokens)
}
}, [fetchPoolTokens, library])
return totalPoolTokens
}
...@@ -108,7 +108,7 @@ export function useToken(tokenAddress: string): Token { ...@@ -108,7 +108,7 @@ export function useToken(tokenAddress: string): Token {
return token return token
} }
export function useAllTokens() { export function useAllTokens(): string[] {
const { chainId } = useWeb3React() const { chainId } = useWeb3React()
const [state] = useTokensContext() const [state] = useTokensContext()
......
...@@ -47,8 +47,8 @@ const Body = styled.div` ...@@ -47,8 +47,8 @@ const Body = styled.div`
max-width: 28rem; max-width: 28rem;
width: 90%; width: 90%;
background: ${({ theme }) => theme.panelBackground}; background: ${({ theme }) => theme.panelBackground};
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.04), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04), box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
0px 24px 32px rgba(0, 0, 0, 0.04); 0px 24px 32px rgba(0, 0, 0, 0.01);
border-radius: 20px; border-radius: 20px;
padding: 2rem 1rem; padding: 2rem 1rem;
` `
......
...@@ -4,34 +4,51 @@ import { ethers } from 'ethers' ...@@ -4,34 +4,51 @@ import { ethers } from 'ethers'
import { parseUnits, parseEther } from '@ethersproject/units' import { parseUnits, parseEther } from '@ethersproject/units'
import { WETH, TokenAmount, JSBI, Percent, Route } from '@uniswap/sdk' import { WETH, TokenAmount, JSBI, Percent, Route } from '@uniswap/sdk'
import SearchModal from '../../components/SearchModal'
import DoubleLogo from '../../components/DoubleLogo' import DoubleLogo from '../../components/DoubleLogo'
import CurrencyInputPanel from '../../components/CurrencyInputPanel' import SearchModal from '../../components/SearchModal'
import PositionCard from '../../components/PositionCard'
import ConfirmationModal from '../../components/ConfirmationModal' import ConfirmationModal from '../../components/ConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import { Text } from 'rebass' import { Text } from 'rebass'
import { Plus } from 'react-feather'
import { ChevronDown } from 'react-feather' import { ChevronDown } from 'react-feather'
import { RowBetween } from '../../components/Row' import { RowBetween } from '../../components/Row'
import { LightCard } from '../../components/Card'
import { ArrowDown, Plus } from 'react-feather'
import { ButtonPrimary, ButtonEmpty } from '../../components/Button'
import { AutoColumn, ColumnCenter } from '../../components/Column' import { AutoColumn, ColumnCenter } from '../../components/Column'
import { ButtonPrimary, ButtonEmpty } from '../../components/Button'
import { useToken } from '../../contexts/Tokens' import { useToken } from '../../contexts/Tokens'
import { useExchange } from '../../contexts/Exchanges'
import { useWeb3React } from '../../hooks' import { useWeb3React } from '../../hooks'
import { useAddressBalance } from '../../contexts/Balances' import { useAddressBalance } from '../../contexts/Balances'
import { useExchangeContract } from '../../hooks'
import { useAddressAllowance } from '../../contexts/Allowances' import { useAddressAllowance } from '../../contexts/Allowances'
import { useTransactionAdder } from '../../contexts/Transactions' import { useTransactionAdder } from '../../contexts/Transactions'
import { useExchange, useTotalSupply } from '../../contexts/Exchanges'
import { BigNumber } from 'ethers/utils'
import { TRANSACTION_TYPE, ROUTER_ADDRESSES } from '../../constants' import { TRANSACTION_TYPE, ROUTER_ADDRESSES } from '../../constants'
import { getRouterContract, calculateGasMargin } from '../../utils' import { getRouterContract, calculateGasMargin } from '../../utils'
// denominated in bips
const ALLOWED_SLIPPAGE = 200
// denominated in seconds
const DEADLINE_FROM_NOW = 60 * 15
const GAS_MARGIN: BigNumber = ethers.utils.bigNumberify(1000)
const Wrapper = styled.div`
position: relative;
`
const ErrorText = styled(Text)` const ErrorText = styled(Text)`
color: ${({ theme, error }) => (error ? theme.salmonRed : theme.chaliceGray)}; color: ${({ theme, error }) => (error ? theme.salmonRed : theme.chaliceGray)};
` `
const FixedBottom = styled.div`
position: absolute;
bottom: -240px;
width: 100%;
`
enum Field { enum Field {
INPUT, INPUT,
OUTPUT OUTPUT
...@@ -120,22 +137,23 @@ function reducer( ...@@ -120,22 +137,23 @@ function reducer(
} }
} }
/**
* @todo should we ever not have prepopulated tokens?
*
*/
export default function AddLiquidity({ token0, token1 }) { export default function AddLiquidity({ token0, token1 }) {
const { account, chainId, library } = useWeb3React() const { account, chainId, library } = useWeb3React()
const routerAddress = ROUTER_ADDRESSES[chainId] const routerAddress: string = ROUTER_ADDRESSES[chainId]
// modal state // modal states
const [showSearch, toggleSearch] = useState(false) const [showSearch, setShowSearch] = useState<boolean>(false)
// state for confirmation popup const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [showConfirm, toggleConfirm] = useState(false) const [pendingConfirmation, setPendingConfirmation] = useState<boolean>(true)
const [pendingConfirmation, toggelPendingConfirmation] = useState(true)
// input state // input state
const [state, dispatch] = useReducer(reducer, initializeAddState(token0, token1)) const [state, dispatch] = useReducer(reducer, initializeAddState(token0, token1))
const { independentField, typedValue, ...fieldData } = state const { independentField, typedValue, ...fieldData } = state
// get derived state
const dependentField = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT const dependentField = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
// get basic SDK entities // get basic SDK entities
...@@ -143,17 +161,26 @@ export default function AddLiquidity({ token0, token1 }) { ...@@ -143,17 +161,26 @@ export default function AddLiquidity({ token0, token1 }) {
[Field.INPUT]: useToken(fieldData[Field.INPUT].address), [Field.INPUT]: useToken(fieldData[Field.INPUT].address),
[Field.OUTPUT]: useToken(fieldData[Field.OUTPUT].address) [Field.OUTPUT]: useToken(fieldData[Field.OUTPUT].address)
} }
// exhchange data
const exchange = useExchange(tokens[Field.INPUT], tokens[Field.OUTPUT]) const exchange = useExchange(tokens[Field.INPUT], tokens[Field.OUTPUT])
const route = exchange ? new Route([exchange], tokens[independentField]) : undefined const route = exchange ? new Route([exchange], tokens[independentField]) : undefined
const totalSupply = useTotalSupply(exchange)
const [noLiquidity, setNoLiquidity] = useState<boolean>(false)
// get user- and token-specific lookup data // state for amount approvals
const inputApproval = useAddressAllowance(account, tokens[Field.INPUT], routerAddress)
const outputApproval = useAddressAllowance(account, tokens[Field.OUTPUT], routerAddress)
const [showInputUnlock, setShowInputUnlock] = useState<boolean>(false)
const [showOutputUnlock, setShowOutputUnlock] = useState<boolean>(false)
// get user-pecific and token-specific lookup data
const userBalances = { const userBalances = {
[Field.INPUT]: useAddressBalance(account, tokens[Field.INPUT]), [Field.INPUT]: useAddressBalance(account, tokens[Field.INPUT]),
[Field.OUTPUT]: useAddressBalance(account, tokens[Field.OUTPUT]) [Field.OUTPUT]: useAddressBalance(account, tokens[Field.OUTPUT])
} }
// check if no exchange or no liquidity // check if no exchange or no liquidity
const [noLiquidity, setNoLiquidity] = useState(false)
useEffect(() => { useEffect(() => {
if ( if (
exchange && exchange &&
...@@ -182,8 +209,8 @@ export default function AddLiquidity({ token0, token1 }) { ...@@ -182,8 +209,8 @@ export default function AddLiquidity({ token0, token1 }) {
} }
}, [independentField, nonrelationalAmounts, tokens, typedValue, noLiquidity]) }, [independentField, nonrelationalAmounts, tokens, typedValue, noLiquidity])
// caclulate the token amounts based on the input
const parsedAmounts: { [field: number]: TokenAmount } = {} const parsedAmounts: { [field: number]: TokenAmount } = {}
//if no liquidity set parsed to non relational, else get dependent calculated amounts
if (noLiquidity) { if (noLiquidity) {
parsedAmounts[independentField] = nonrelationalAmounts[independentField] parsedAmounts[independentField] = nonrelationalAmounts[independentField]
parsedAmounts[dependentField] = nonrelationalAmounts[dependentField] parsedAmounts[dependentField] = nonrelationalAmounts[dependentField]
...@@ -199,7 +226,6 @@ export default function AddLiquidity({ token0, token1 }) { ...@@ -199,7 +226,6 @@ export default function AddLiquidity({ token0, token1 }) {
} }
} }
// get the price data and update dependent field
if ( if (
route && route &&
!noLiquidity && !noLiquidity &&
...@@ -215,47 +241,15 @@ export default function AddLiquidity({ token0, token1 }) { ...@@ -215,47 +241,15 @@ export default function AddLiquidity({ token0, token1 }) {
[dependentField]: parsedAmounts[dependentField] ? parsedAmounts[dependentField]?.toSignificant(8) : '' [dependentField]: parsedAmounts[dependentField] ? parsedAmounts[dependentField]?.toSignificant(8) : ''
} }
// pool token data
const [totalPoolTokens, setTotalPoolTokens] = useState<TokenAmount>()
// move this to a hook
const exchangeContract = useExchangeContract(exchange?.liquidityToken.address)
const fetchPoolTokens = useCallback(async () => {
exchangeContract
.deployed()
.then(() => {
if (exchangeContract) {
exchangeContract.totalSupply().then(totalSupply => {
if (totalSupply !== undefined && exchange?.liquidityToken?.decimals) {
const supplyFormatted = JSBI.BigInt(totalSupply)
const tokenSupplyFormatted = new TokenAmount(exchange?.liquidityToken, supplyFormatted)
setTotalPoolTokens(tokenSupplyFormatted)
}
})
}
})
.catch(e => {
console.log('error')
})
}, [exchangeContract])
useEffect(() => {
fetchPoolTokens()
library.on('block', fetchPoolTokens)
return () => {
library.removeListener('block', fetchPoolTokens)
}
}, [fetchPoolTokens, library])
// check for estimated liquidity minted // check for estimated liquidity minted
const liquidityMinted = const liquidityMinted =
!!exchange && !!parsedAmounts[Field.INPUT] && !!parsedAmounts[Field.OUTPUT] && !!totalPoolTokens && exchange !!exchange && !!parsedAmounts[Field.INPUT] && !!parsedAmounts[Field.OUTPUT] && !!totalSupply
? exchange.getLiquidityMinted(totalPoolTokens, parsedAmounts[Field.INPUT], parsedAmounts[Field.OUTPUT]) ? exchange.getLiquidityMinted(totalSupply, parsedAmounts[Field.INPUT], parsedAmounts[Field.OUTPUT])
: undefined : undefined
const poolTokenPercentage = const poolTokenPercentage =
!!liquidityMinted && !!totalPoolTokens !!liquidityMinted && !!totalSupply
? new Percent(liquidityMinted.raw, totalPoolTokens.add(liquidityMinted).raw) ? new Percent(liquidityMinted.raw, totalSupply.add(liquidityMinted).raw)
: undefined : undefined
const onTokenSelection = useCallback((field: Field, address: string) => { const onTokenSelection = useCallback((field: Field, address: string) => {
...@@ -269,215 +263,218 @@ export default function AddLiquidity({ token0, token1 }) { ...@@ -269,215 +263,218 @@ export default function AddLiquidity({ token0, token1 }) {
dispatch({ type: AddAction.TYPE, payload: { field, typedValue } }) dispatch({ type: AddAction.TYPE, payload: { field, typedValue } })
}, []) }, [])
const onMaxInput = useCallback((typedValue: string) => { const onMax = useCallback((typedValue: string, field) => {
dispatch({
type: AddAction.TYPE,
payload: {
field: Field.INPUT,
typedValue
}
})
}, [])
const onMaxOutput = useCallback((typedValue: string) => {
dispatch({ dispatch({
type: AddAction.TYPE, type: AddAction.TYPE,
payload: { payload: {
field: Field.OUTPUT, field: field,
typedValue typedValue
} }
}) })
}, []) }, [])
const MIN_ETHER = new TokenAmount(WETH[chainId], JSBI.BigInt(parseEther('.01'))) const MIN_ETHER = new TokenAmount(WETH[chainId], JSBI.BigInt(parseEther('.01')))
const maxAmountInput =
!!userBalances[Field.INPUT] &&
JSBI.greaterThan(
userBalances[Field.INPUT].raw,
tokens[Field.INPUT].equals(WETH[chainId]) ? MIN_ETHER.raw : JSBI.BigInt(0)
)
? tokens[Field.INPUT].equals(WETH[chainId])
? userBalances[Field.INPUT].subtract(MIN_ETHER)
: userBalances[Field.INPUT]
: undefined
const atMaxAmountInput =
!!maxAmountInput && !!parsedAmounts[Field.INPUT]
? JSBI.equal(maxAmountInput.raw, parsedAmounts[Field.INPUT].raw)
: undefined
const maxAmountOutput = // get the max amounts user can add
!!userBalances[Field.OUTPUT] && const [maxAmountInput, maxAmountOutput] = [Field.INPUT, Field.OUTPUT].map(index => {
JSBI.greaterThan( const field = Field[index]
userBalances[Field.OUTPUT].raw, return !!userBalances[Field[field]] &&
tokens[Field.OUTPUT].equals(WETH[chainId]) ? MIN_ETHER.raw : JSBI.BigInt(0) JSBI.greaterThan(
) userBalances[Field[field]].raw,
? tokens[Field.OUTPUT].equals(WETH[chainId]) tokens[Field[field]].equals(WETH[chainId]) ? MIN_ETHER.raw : JSBI.BigInt(0)
? userBalances[Field.OUTPUT].subtract(MIN_ETHER) )
: userBalances[Field.OUTPUT] ? tokens[Field[field]].equals(WETH[chainId])
? userBalances[Field[field]].subtract(MIN_ETHER)
: userBalances[Field[field]]
: undefined : undefined
})
const atMaxAmountOutput = const [atMaxAmountInput, atMaxAmountOutput] = [Field.INPUT, Field.OUTPUT].map(index => {
!!maxAmountOutput && !!parsedAmounts[Field.OUTPUT] const field = Field[index]
? JSBI.equal(maxAmountOutput.raw, parsedAmounts[Field.OUTPUT].raw) const maxAmount = index === Field.INPUT ? maxAmountInput : maxAmountOutput
return !!maxAmount && !!parsedAmounts[Field[field]]
? JSBI.lessThanOrEqual(maxAmount.raw, parsedAmounts[Field[field]].raw)
: undefined : undefined
})
const inputApproval = useAddressAllowance(account, tokens[Field.INPUT], routerAddress)
const outputApproval = useAddressAllowance(account, tokens[Field.OUTPUT], routerAddress)
const [showInputUnlock, setShowInputUnlock] = useState(false)
const [showOutputUnlock, setShowOutputUnlock] = useState(false)
// monitor parsed amounts and update unlocked buttons // monitor parsed amounts and update unlocked buttons
useEffect(() => { useEffect(() => {
if ( setShowInputUnlock(
parsedAmounts[Field.INPUT] && parsedAmounts[Field.INPUT] && inputApproval && JSBI.greaterThan(parsedAmounts[Field.INPUT].raw, inputApproval.raw)
inputApproval && )
JSBI.greaterThan(parsedAmounts[Field.INPUT].raw, inputApproval.raw) setShowOutputUnlock(
) {
setShowInputUnlock(true)
} else {
setShowInputUnlock(false)
}
if (
parsedAmounts[Field.OUTPUT] && parsedAmounts[Field.OUTPUT] &&
outputApproval && outputApproval &&
JSBI.greaterThan(parsedAmounts[Field.OUTPUT]?.raw, outputApproval?.raw) JSBI.greaterThan(parsedAmounts[Field.OUTPUT].raw, outputApproval.raw)
) { )
setShowOutputUnlock(true)
} else {
setShowOutputUnlock(false)
}
}, [inputApproval, outputApproval, parsedAmounts]) }, [inputApproval, outputApproval, parsedAmounts])
// errors // errors
const [generalError, setGeneralError] = useState()
const [inputError, setInputError] = useState() const [inputError, setInputError] = useState()
const [outputError, setOutputError] = useState() const [outputError, setOutputError] = useState()
const [errorText, setErrorText] = useState(' ')
const [isError, setIsError] = useState(false) const [isError, setIsError] = useState(false)
const [isValid, setIsValid] = useState(false)
// update errors live // update errors live
useEffect(() => { useEffect(() => {
// reset errors // reset errors
setGeneralError(null)
setInputError(null) setInputError(null)
setOutputError(null) setOutputError(null)
setIsError(false) setIsError(false)
setIsValid(true)
if (!parsedAmounts[Field.INPUT]) {
setGeneralError('Enter an amount to continue')
setIsValid(false)
}
if (!parsedAmounts[Field.OUTPUT]) {
setGeneralError('Enter an amount to continue')
setIsValid(false)
}
if (showInputUnlock) { if (showInputUnlock) {
setInputError('Need to approve amount on input.') setInputError('Need to approve amount on input.')
setIsValid(false)
} }
if (showOutputUnlock) { if (showOutputUnlock) {
setOutputError('Need to approve amount on output.') setOutputError('Need to approve amount on output.')
setIsValid(false)
} }
if (parseFloat(parsedAmounts?.[Field.INPUT]?.toExact()) > parseFloat(userBalances?.[Field.INPUT]?.toExact())) { if (
parsedAmounts?.[Field.INPUT] &&
userBalances?.[Field.INPUT] &&
JSBI.greaterThan(parsedAmounts?.[Field.INPUT]?.raw, userBalances?.[Field.INPUT]?.raw)
) {
setInputError('Insufficient balance.') setInputError('Insufficient balance.')
setIsError(true) setIsError(true)
setIsValid(false)
} }
if (parseFloat(parsedAmounts?.[Field.OUTPUT]?.toExact()) > parseFloat(userBalances?.[Field.OUTPUT]?.toExact())) { if (
parsedAmounts?.[Field.OUTPUT] &&
userBalances?.[Field.OUTPUT] &&
JSBI.greaterThan(parsedAmounts?.[Field.OUTPUT]?.raw, userBalances?.[Field.OUTPUT]?.raw)
) {
setOutputError('Insufficient balance.') setOutputError('Insufficient balance.')
setIsError(true) setIsError(true)
setIsValid(false)
} }
}, [parsedAmounts, showInputUnlock, showOutputUnlock, userBalances]) }, [parsedAmounts, showInputUnlock, showOutputUnlock, userBalances])
// set error text based on all errors
useEffect(() => {
setErrorText(null)
if (!parsedAmounts[Field.INPUT]) {
setErrorText('Enter an amount to continue')
} else if (outputError) {
setErrorText(outputError)
} else if (inputError) {
setErrorText(inputError)
return
}
}, [inputError, outputError, parsedAmounts])
// error state for button
const isValid = !errorText
// state for txn // state for txn
const addTransaction = useTransactionAdder() const addTransaction = useTransactionAdder()
const [txHash, setTxHash] = useState() const [txHash, setTxHash] = useState()
function hex(value: JSBI) {
return ethers.utils.bigNumberify(value.toString())
}
function calculateSlippageAmount(value: TokenAmount): JSBI[] {
if (value && value.raw) {
const offset = JSBI.divide(JSBI.multiply(JSBI.BigInt(ALLOWED_SLIPPAGE), value.raw), JSBI.BigInt(10000))
return [JSBI.subtract(value.raw, offset), JSBI.add(value.raw, offset)]
}
return null
}
async function onAdd() { async function onAdd() {
const router = getRouterContract(chainId, library, account) const router = getRouterContract(chainId, library, account)
const minTokenInput = JSBI.divide(JSBI.multiply(JSBI.BigInt(99), parsedAmounts[Field.INPUT].raw), JSBI.BigInt(100))
const minTokenOutput = JSBI.divide(
JSBI.multiply(JSBI.BigInt(99), parsedAmounts[Field.OUTPUT].raw),
JSBI.BigInt(100)
)
const args = [ const minTokenInput = calculateSlippageAmount(parsedAmounts[Field.INPUT])[0]
tokens[Field.INPUT].address, const minTokenOutput = calculateSlippageAmount(parsedAmounts[Field.OUTPUT])[0]
tokens[Field.OUTPUT].address,
parsedAmounts[Field.INPUT].raw.toString(), const deadline = Math.ceil(Date.now() / 1000) + DEADLINE_FROM_NOW
parsedAmounts[Field.OUTPUT].raw.toString(),
noLiquidity ? parsedAmounts[Field.INPUT].raw.toString() : minTokenInput.toString(), let method, estimate, args, value
noLiquidity ? parsedAmounts[Field.OUTPUT].raw.toString() : minTokenOutput.toString(),
account.toString(), if (tokens[Field.INPUT] === WETH[chainId] || tokens[Field.OUTPUT] === WETH[chainId]) {
1739591241 method = router.addLiquidityETH
] estimate = router.estimate.addLiquidityETH
args = [
const estimatedGasLimit = await router.estimate.addLiquidity(...args, { tokens[Field.OUTPUT] === WETH[chainId] ? tokens[Field.INPUT].address : tokens[Field.OUTPUT].address, // token
value: ethers.constants.Zero tokens[Field.OUTPUT] === WETH[chainId] // token desired
? parsedAmounts[Field.INPUT].raw.toString()
: parsedAmounts[Field.OUTPUT].raw.toString(),
tokens[Field.OUTPUT] === WETH[chainId] ? minTokenInput.toString() : minTokenOutput.toString(), // token min
tokens[Field.OUTPUT] === WETH[chainId] ? minTokenOutput.toString() : minTokenInput.toString(), // eth min
account,
deadline
]
value = hex(
tokens[Field.OUTPUT] === WETH[chainId] // eth desired
? parsedAmounts[Field.OUTPUT].raw
: parsedAmounts[Field.INPUT].raw
)
} else {
method = router.addLiquidity
estimate = router.estimate.addLiquidity
args = [
tokens[Field.INPUT].address,
tokens[Field.OUTPUT].address,
parsedAmounts[Field.INPUT].raw.toString(),
parsedAmounts[Field.OUTPUT].raw.toString(),
noLiquidity ? parsedAmounts[Field.INPUT].raw.toString() : minTokenInput.toString(),
noLiquidity ? parsedAmounts[Field.OUTPUT].raw.toString() : minTokenOutput.toString(),
account,
deadline
]
value = ethers.constants.Zero
}
const estimatedGasLimit = await estimate(...args, {
value: value
}) })
const GAS_MARGIN = ethers.utils.bigNumberify(1000) method(...args, {
router gasLimit: calculateGasMargin(estimatedGasLimit, GAS_MARGIN),
.addLiquidity(...args, { value: value
gasLimit: calculateGasMargin(estimatedGasLimit, GAS_MARGIN) })
})
.then(response => { .then(response => {
setTxHash(response) setTxHash(response.hash)
addTransaction(response) addTransaction(response)
toggelPendingConfirmation(false) setPendingConfirmation(false)
}) })
.catch(e => { .catch((e: Error) => {
toggleConfirm(false) console.log(e)
setShowConfirm(false)
}) })
} }
return ( return (
<> <Wrapper>
<ConfirmationModal <ConfirmationModal
isOpen={showConfirm} isOpen={showConfirm}
onDismiss={() => { onDismiss={() => {
toggleConfirm(false) setShowConfirm(false)
}} }}
liquidityAmount={liquidityMinted} liquidityAmount={liquidityMinted}
amount0={ amount0={parsedAmounts[Field.INPUT]}
parsedAmounts[independentField]?.token.equals(exchange?.token0) amount1={parsedAmounts[Field.OUTPUT]}
? parsedAmounts[independentField]
: parsedAmounts[dependentField]
}
amount1={
parsedAmounts[independentField]?.token.equals(exchange?.token0)
? parsedAmounts[dependentField]
: parsedAmounts[independentField]
}
poolTokenPercentage={poolTokenPercentage} poolTokenPercentage={poolTokenPercentage}
price={route?.midPrice && route?.midPrice?.raw?.denominator} price={route?.midPrice && route?.midPrice?.raw?.denominator}
transactionType={TRANSACTION_TYPE.ADD} transactionType={TRANSACTION_TYPE.ADD}
contractCall={onAdd} contractCall={onAdd}
pendingConfirmation={pendingConfirmation} pendingConfirmation={pendingConfirmation}
hash={txHash ? txHash.hash : ''} hash={txHash ? txHash : ''}
/> />
<SearchModal <SearchModal
isOpen={showSearch} isOpen={showSearch}
onDismiss={() => { onDismiss={() => {
toggleSearch(false) setShowSearch(false)
}} }}
/> />
<AutoColumn gap="20px"> <AutoColumn gap="20px">
<ButtonEmpty <ButtonEmpty
padding={'1rem'} padding={'1rem'}
onClick={() => { onClick={() => {
toggleSearch(true) setShowSearch(true)
}} }}
> >
<RowBetween> <RowBetween>
<DoubleLogo a0={exchange?.token0?.address || ''} a1={exchange?.token1?.address || ''} size={24} /> <DoubleLogo a0={exchange?.token0?.address || ''} a1={exchange?.token1?.address || ''} size={24} />
<Text fontSize={20}> <Text fontSize={20}>
{exchange?.token0?.symbol && exchange?.token1?.symbol {exchange?.token0 && exchange?.token1
? exchange?.token0?.symbol + ' / ' + exchange?.token1?.symbol + ' Pool' ? exchange.token0.symbol + ' / ' + exchange.token1.symbol + ' Pool'
: ''} : ''}
</Text> </Text>
<ChevronDown size={24} /> <ChevronDown size={24} />
...@@ -498,7 +495,7 @@ export default function AddLiquidity({ token0, token1 }) { ...@@ -498,7 +495,7 @@ export default function AddLiquidity({ token0, token1 }) {
value={formattedAmounts[Field.INPUT]} value={formattedAmounts[Field.INPUT]}
onUserInput={onUserInput} onUserInput={onUserInput}
onMax={() => { onMax={() => {
maxAmountInput && onMaxInput(maxAmountInput.toExact()) maxAmountInput && onMax(maxAmountInput.toExact(), Field.INPUT)
}} }}
atMax={atMaxAmountInput} atMax={atMaxAmountInput}
token={tokens[Field.INPUT]} token={tokens[Field.INPUT]}
...@@ -517,7 +514,7 @@ export default function AddLiquidity({ token0, token1 }) { ...@@ -517,7 +514,7 @@ export default function AddLiquidity({ token0, token1 }) {
value={formattedAmounts[Field.OUTPUT]} value={formattedAmounts[Field.OUTPUT]}
onUserInput={onUserInput} onUserInput={onUserInput}
onMax={() => { onMax={() => {
onMaxOutput(maxAmountOutput.toExact()) onMax(maxAmountOutput.toExact(), Field.OUTPUT)
}} }}
atMax={atMaxAmountOutput} atMax={atMaxAmountOutput}
token={tokens[Field.OUTPUT]} token={tokens[Field.OUTPUT]}
...@@ -528,39 +525,21 @@ export default function AddLiquidity({ token0, token1 }) { ...@@ -528,39 +525,21 @@ export default function AddLiquidity({ token0, token1 }) {
showUnlock={showOutputUnlock} showUnlock={showOutputUnlock}
disableTokenSelect disableTokenSelect
/> />
<ColumnCenter> <RowBetween>
<ArrowDown size="16" color="#888D9B" /> Rate:
</ColumnCenter> <div>
<LightCard> 1 {tokens[independentField].symbol} = {route?.midPrice?.toSignificant(6)}
<AutoColumn gap="10px"> {tokens[dependentField].symbol}
<RowBetween> </div>
Minted pool tokens: </RowBetween>
<div>{liquidityMinted ? liquidityMinted.toExact() : '-'}</div>
</RowBetween>
<RowBetween>
Minted pool share:
<div>{poolTokenPercentage ? +poolTokenPercentage.toFixed(4) + '%' : '-'}</div>
</RowBetween>
<RowBetween>
Rate:
<div>
1 {exchange?.token0.symbol} ={' '}
{independentField === Field.OUTPUT
? route?.midPrice.invert().toSignificant(6)
: route?.midPrice.toSignificant(6)}{' '}
{exchange?.token1.symbol}
</div>
</RowBetween>
</AutoColumn>
</LightCard>
<ColumnCenter style={{ height: '20px' }}> <ColumnCenter style={{ height: '20px' }}>
<ErrorText fontSize={12} error={isError}> <ErrorText fontSize={12} error={isError}>
{errorText && errorText} {generalError ? generalError : inputError ? inputError : outputError ? outputError : ''}
</ErrorText> </ErrorText>
</ColumnCenter> </ColumnCenter>
<ButtonPrimary <ButtonPrimary
onClick={() => { onClick={() => {
toggleConfirm(true) setShowConfirm(true)
}} }}
disabled={!isValid} disabled={!isValid}
> >
...@@ -568,7 +547,15 @@ export default function AddLiquidity({ token0, token1 }) { ...@@ -568,7 +547,15 @@ export default function AddLiquidity({ token0, token1 }) {
Supply Supply
</Text> </Text>
</ButtonPrimary> </ButtonPrimary>
<FixedBottom>
<PositionCard
exchangeAddress={exchange?.liquidityToken?.address}
token0={tokens[Field.INPUT]}
token1={tokens[Field.OUTPUT]}
minimal={true}
/>
</FixedBottom>
</AutoColumn> </AutoColumn>
</> </Wrapper>
) )
} }
import React, { useReducer, useState, useCallback, useEffect } from 'react' import React, { useReducer, useState, useCallback, useEffect } from 'react'
import { WETH, TokenAmount, JSBI, Route } from '@uniswap/sdk'
import { ethers } from 'ethers'
import { parseUnits, parseEther } from '@ethersproject/units'
import styled from 'styled-components' import styled from 'styled-components'
import { ethers } from 'ethers'
import { parseUnits } from '@ethersproject/units'
import { TokenAmount, JSBI, Route, WETH } from '@uniswap/sdk'
import { Text } from 'rebass' import Slider from '../../components/Slider'
import { ChevronDown } from 'react-feather'
import { ButtonPrimary, ButtonEmpty } from '../../components/Button'
import ConfirmationModal from '../../components/ConfirmationModal'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import { RowBetween } from '../../components/Row'
import DoubleLogo from '../../components/DoubleLogo' import DoubleLogo from '../../components/DoubleLogo'
import { ArrowDown, Plus } from 'react-feather' import SearchModal from '../../components/SearchModal'
import PositionCard from '../../components/PositionCard'
import ConfirmationModal from '../../components/ConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel' import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import { Text } from 'rebass'
import { LightCard } from '../../components/Card' import { LightCard } from '../../components/Card'
import SearchModal from '../../components/SearchModal' import { ChevronDown } from 'react-feather'
import { ArrowDown, Plus } from 'react-feather'
import Row, { RowBetween, RowFixed, RowFlat } from '../../components/Row'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import { ButtonPrimary, ButtonEmpty } from '../../components/Button'
import { useWeb3React } from '../../hooks'
import { useToken } from '../../contexts/Tokens' import { useToken } from '../../contexts/Tokens'
import { useAddressBalance, useAllBalances } from '../../contexts/Balances' import { useWeb3React } from '../../hooks'
import { useExchange } from '../../contexts/Exchanges' import { useAllBalances } from '../../contexts/Balances'
import { useExchangeContract } from '../../hooks'
import { useTransactionAdder } from '../../contexts/Transactions' import { useTransactionAdder } from '../../contexts/Transactions'
import { useExchangeContract } from '../../hooks'
import { useExchange, useTotalSupply } from '../../contexts/Exchanges'
import { BigNumber } from 'ethers/utils'
import { splitSignature } from '@ethersproject/bytes'
import { TRANSACTION_TYPE } from '../../constants' import { TRANSACTION_TYPE } from '../../constants'
import { ROUTER_ADDRESSES } from '../../constants'
import { getRouterContract, calculateGasMargin } from '../../utils' import { getRouterContract, calculateGasMargin } from '../../utils'
import { splitSignature } from '@ethersproject/bytes' import TokenLogo from '../../components/TokenLogo'
// denominated in seconds
const DEADLINE_FROM_NOW = 60 * 15
const GAS_MARGIN: BigNumber = ethers.utils.bigNumberify(1000)
const Wrapper = styled.div`
position: relative;
`
const FixedBottom = styled.div`
position: absolute;
bottom: -240px;
width: 100%;
`
const ErrorText = styled(Text)` const ClickableText = styled(Text)`
color: ${({ theme, error }) => (error ? theme.salmonRed : theme.chaliceGray)}; :hover {
cursor: pointer;
}
`
const MaxButton = styled.button`
padding: 0.5rem 1rem;
background-color: ${({ theme }) => theme.zumthorBlue};
border: 1px solid ${({ theme }) => theme.zumthorBlue};
border-radius: 0.5rem;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
margin-right: 0.5rem;
color: ${({ theme }) => theme.royalBlue};
:hover {
border: 1px solid ${({ theme }) => theme.royalBlue};
}
:focus {
border: 1px solid ${({ theme }) => theme.royalBlue};
outline: none;
}
` `
enum Field { enum Field {
PERCENTAGE,
POOL, POOL,
INPUT, INPUT,
OUTPUT OUTPUT
...@@ -53,7 +95,7 @@ interface RemoveState { ...@@ -53,7 +95,7 @@ interface RemoveState {
function initializeRemoveState(inputAddress?: string, outputAddress?: string): RemoveState { function initializeRemoveState(inputAddress?: string, outputAddress?: string): RemoveState {
return { return {
independentField: Field.INPUT, independentField: Field.PERCENTAGE,
typedValue: '', typedValue: '',
[Field.POOL]: { [Field.POOL]: {
address: '' address: ''
...@@ -110,12 +152,12 @@ function reducer( ...@@ -110,12 +152,12 @@ function reducer(
export default function RemoveLiquidity({ token0, token1 }) { export default function RemoveLiquidity({ token0, token1 }) {
// console.log('DEBUG: Rendering') // console.log('DEBUG: Rendering')
const { account, chainId, library, connector } = useWeb3React() const { account, chainId, library } = useWeb3React()
const routerAddress = ROUTER_ADDRESSES[chainId]
// modal state // modal state
const [showSearch, toggleSearch] = useState(false) const [showSearch, toggleSearch] = useState(false)
const [pendingConfirmation, setPendingConfirmation] = useState(true)
const [pendingConfirmation, toggelPendingConfirmation] = useState(false)
// input state // input state
const [state, dispatch] = useReducer(reducer, initializeRemoveState(token0, token1)) const [state, dispatch] = useReducer(reducer, initializeRemoveState(token0, token1))
...@@ -134,53 +176,13 @@ export default function RemoveLiquidity({ token0, token1 }) { ...@@ -134,53 +176,13 @@ export default function RemoveLiquidity({ token0, token1 }) {
const exchangeContract = useExchangeContract(exchange?.liquidityToken.address) const exchangeContract = useExchangeContract(exchange?.liquidityToken.address)
// pool token data // pool token data
const [totalPoolTokens, setTotalPoolTokens] = useState<TokenAmount>() const totalPoolTokens = useTotalSupply(exchange)
// get user- and token-specific lookup data
const userBalances = {
[Field.INPUT]: useAddressBalance(account, tokens[Field.INPUT]),
[Field.OUTPUT]: useAddressBalance(account, tokens[Field.OUTPUT])
}
const allBalances = useAllBalances() const allBalances = useAllBalances()
const userLiquidity = allBalances?.[account]?.[exchange.liquidityToken.address] const userLiquidity = allBalances?.[account]?.[exchange?.liquidityToken?.address]
// state for confirmation popup // state for confirmation popup
const [showConfirm, toggleConfirm] = useState(false) const [showConfirm, setShowConfirm] = useState(false)
// errors
const [inputError, setInputError] = useState()
const [outputError, setOutputError] = useState()
const [poolTokenError, setPoolTokenError] = useState()
const [errorText, setErrorText] = useState(' ')
const [isError, setIsError] = useState(false)
const fetchPoolTokens = useCallback(() => {
if (exchangeContract && exchange && exchange.liquidityToken) {
exchangeContract.totalSupply().then(totalSupply => {
if (totalSupply !== undefined) {
const supplyFormatted = JSBI.BigInt(totalSupply)
const tokenSupplyFormatted = new TokenAmount(exchange.liquidityToken, supplyFormatted)
setTotalPoolTokens(tokenSupplyFormatted)
}
})
}
/**
* @todo
*
* when exchange is used here enter infinite loop
*
*/
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [exchangeContract])
useEffect(() => {
fetchPoolTokens()
library.on('block', fetchPoolTokens)
return () => {
library.removeListener('block', fetchPoolTokens)
}
}, [fetchPoolTokens, library])
const TokensDeposited = { const TokensDeposited = {
[Field.INPUT]: [Field.INPUT]:
...@@ -196,23 +198,76 @@ export default function RemoveLiquidity({ token0, token1 }) { ...@@ -196,23 +198,76 @@ export default function RemoveLiquidity({ token0, token1 }) {
} }
const route = exchange const route = exchange
? new Route([exchange], independentField === Field.POOL ? tokens[Field.INPUT] : tokens[independentField]) ? new Route(
[exchange],
independentField === Field.POOL || independentField === Field.PERCENTAGE
? tokens[Field.OUTPUT]
: tokens[independentField]
)
: undefined : undefined
const onTokenSelection = useCallback((field: Field, address: string) => {
dispatch({
type: RemoveAction.SELECT_TOKEN,
payload: { field, address }
})
}, [])
// update input value when user types
const onUserInput = useCallback((field: Field, typedValue: string) => {
dispatch({ type: RemoveAction.TYPE, payload: { field, typedValue } })
}, [])
// used for percentage based amount setting
const [percentageAmount, setPercentageAmount] = useState(100)
const handleSliderChange = (event, newValue) => {
setPercentageAmount(newValue)
onUserInput(Field.PERCENTAGE, undefined)
}
// parse the amounts based on input // parse the amounts based on input
const parsedAmounts: { [field: number]: TokenAmount } = {} const parsedAmounts: { [field: number]: TokenAmount } = {}
// try to parse typed value // try to parse typed value
if (independentField === Field.INPUT) { if (
independentField === Field.PERCENTAGE &&
userLiquidity &&
exchange &&
totalPoolTokens &&
tokens[Field.INPUT] &&
tokens[Field.OUTPUT]
) {
const formattedPercentage = JSBI.BigInt(percentageAmount)
const liquidityAmount = JSBI.divide(JSBI.multiply(userLiquidity.raw, formattedPercentage), JSBI.BigInt(100))
parsedAmounts[Field.POOL] = new TokenAmount(exchange?.liquidityToken, liquidityAmount)
parsedAmounts[Field.INPUT] = exchange.getLiquidityValue(
tokens[Field.INPUT],
totalPoolTokens,
parsedAmounts[Field.POOL],
false
)
parsedAmounts[Field.OUTPUT] = exchange.getLiquidityValue(
tokens[Field.OUTPUT],
totalPoolTokens,
parsedAmounts[Field.POOL],
false
)
} else if (independentField === Field.INPUT) {
if (typedValue !== '' && typedValue !== '.' && tokens[Field.INPUT] && exchange && userLiquidity) { if (typedValue !== '' && typedValue !== '.' && tokens[Field.INPUT] && exchange && userLiquidity) {
try { try {
const typedValueParsed = parseUnits(typedValue, tokens[Field.INPUT].decimals).toString() const typedValueParsed = parseUnits(typedValue, tokens[Field.INPUT].decimals).toString()
if (typedValueParsed !== '0') { if (typedValueParsed !== '0') {
parsedAmounts[Field.INPUT] = new TokenAmount(tokens[Field.INPUT], typedValueParsed) // first get the exact percentage of tokens deposited
parsedAmounts[Field.OUTPUT] = route.midPrice.quote(parsedAmounts[Field.INPUT]) const inputTokenAmount = new TokenAmount(tokens[Field.INPUT], typedValueParsed)
parsedAmounts[Field.POOL] = exchange.getLiquidityMinted( const ratio = JSBI.divide(inputTokenAmount.raw, TokensDeposited[Field.INPUT].raw)
const partialLiquidity = JSBI.multiply(userLiquidity.raw, ratio)
const liquidityTokenAmount = new TokenAmount(exchange.liquidityToken, partialLiquidity)
parsedAmounts[Field.POOL] = liquidityTokenAmount
parsedAmounts[Field.OUTPUT] = exchange.getLiquidityValue(
tokens[Field.OUTPUT],
totalPoolTokens, totalPoolTokens,
parsedAmounts[Field.INPUT], parsedAmounts[Field.POOL],
parsedAmounts[Field.OUTPUT] false
) )
} }
} catch (error) { } catch (error) {
...@@ -225,12 +280,16 @@ export default function RemoveLiquidity({ token0, token1 }) { ...@@ -225,12 +280,16 @@ export default function RemoveLiquidity({ token0, token1 }) {
try { try {
const typedValueParsed = parseUnits(typedValue, tokens[Field.OUTPUT].decimals).toString() const typedValueParsed = parseUnits(typedValue, tokens[Field.OUTPUT].decimals).toString()
if (typedValueParsed !== '0') { if (typedValueParsed !== '0') {
parsedAmounts[Field.OUTPUT] = new TokenAmount(tokens[Field.OUTPUT], typedValueParsed) const inputTokenAmount = new TokenAmount(tokens[Field.OUTPUT], typedValueParsed)
parsedAmounts[Field.INPUT] = route.midPrice.quote(parsedAmounts[Field.OUTPUT]) const ratio = JSBI.divide(inputTokenAmount.raw, TokensDeposited[Field.OUTPUT].raw)
parsedAmounts[Field.POOL] = exchange.getLiquidityMinted( const partialLiquidity = JSBI.multiply(userLiquidity.raw, ratio)
const liquidityTokenAmount = new TokenAmount(exchange.liquidityToken, partialLiquidity)
parsedAmounts[Field.POOL] = liquidityTokenAmount
parsedAmounts[Field.INPUT] = exchange.getLiquidityValue(
tokens[Field.INPUT],
totalPoolTokens, totalPoolTokens,
parsedAmounts[Field.INPUT], parsedAmounts[Field.POOL],
parsedAmounts[Field.OUTPUT] false
) )
} }
} catch (error) { } catch (error) {
...@@ -238,16 +297,21 @@ export default function RemoveLiquidity({ token0, token1 }) { ...@@ -238,16 +297,21 @@ export default function RemoveLiquidity({ token0, token1 }) {
console.error(error) console.error(error)
} }
} }
} else { } else if (independentField === Field.POOL) {
if (typedValue !== '' && typedValue !== '.' && exchange) { if (typedValue !== '' && typedValue !== '.' && exchange) {
try { try {
const typedValueParsed = parseUnits(typedValue, exchange?.liquidityToken.decimals).toString() const typedValueParsed = parseUnits(typedValue, exchange?.liquidityToken.decimals).toString()
if (typedValueParsed !== '0') { if (typedValueParsed !== '0') {
parsedAmounts[Field.POOL] = new TokenAmount(exchange?.liquidityToken, typedValueParsed) parsedAmounts[Field.POOL] = new TokenAmount(exchange?.liquidityToken, typedValueParsed)
if ( if (
!JSBI.lessThanOrEqual(parsedAmounts[Field.POOL].raw, totalPoolTokens.raw) || (parsedAmounts[Field.POOL] &&
totalPoolTokens &&
!JSBI.lessThanOrEqual(parsedAmounts[Field.POOL].raw, totalPoolTokens.raw)) ||
!JSBI.lessThanOrEqual(parsedAmounts[Field.POOL].raw, userLiquidity.raw) !JSBI.lessThanOrEqual(parsedAmounts[Field.POOL].raw, userLiquidity.raw)
) { ) {
/**
* do some error catching on amount?
*/
} else { } else {
parsedAmounts[Field.INPUT] = exchange.getLiquidityValue( parsedAmounts[Field.INPUT] = exchange.getLiquidityValue(
tokens[Field.INPUT], tokens[Field.INPUT],
...@@ -292,97 +356,64 @@ export default function RemoveLiquidity({ token0, token1 }) { ...@@ -292,97 +356,64 @@ export default function RemoveLiquidity({ token0, token1 }) {
: '' : ''
} }
const onTokenSelection = useCallback((field: Field, address: string) => { const onMax = useCallback((typedValue: string, field) => {
dispatch({
type: RemoveAction.SELECT_TOKEN,
payload: { field, address }
})
}, [])
// update input value when user types
const onUserInput = useCallback((field: Field, typedValue: string) => {
dispatch({ type: RemoveAction.TYPE, payload: { field, typedValue } })
}, [])
const onMaxInput = useCallback((typedValue: string) => {
dispatch({
type: RemoveAction.TYPE,
payload: {
field: Field.INPUT,
typedValue
}
})
}, [])
const onMaxOutput = useCallback((typedValue: string) => {
dispatch({
type: RemoveAction.TYPE,
payload: {
field: Field.OUTPUT,
typedValue
}
})
}, [])
const onMaxPool = useCallback((typedValue: string) => {
dispatch({ dispatch({
type: RemoveAction.TYPE, type: RemoveAction.TYPE,
payload: { payload: {
field: Field.POOL, field: field,
typedValue typedValue
} }
}) })
}, []) }, [])
const MIN_ETHER = new TokenAmount(WETH[chainId], JSBI.BigInt(parseEther('.01'))) // get the max amounts user can add
const maxAmountInput = const maxAmountPoolToken = userLiquidity
TokensDeposited[Field.INPUT] && const [maxAmountInput, maxAmountOutput] = [Field.INPUT, Field.OUTPUT].map(index => {
JSBI.greaterThan( const field = Field[index]
TokensDeposited[Field.INPUT].raw, return !!TokensDeposited[Field[field]] && JSBI.greaterThan(TokensDeposited[Field[field]].raw, JSBI.BigInt(0))
tokens[Field.INPUT].equals(WETH[chainId]) ? MIN_ETHER.raw : JSBI.BigInt(0) ? TokensDeposited[Field[field]]
)
? tokens[Field.INPUT].equals(WETH[chainId])
? TokensDeposited[Field.INPUT].subtract(MIN_ETHER)
: TokensDeposited[Field.INPUT]
: undefined
const atMaxAmountInput =
!!maxAmountInput && !!parsedAmounts[Field.INPUT]
? JSBI.equal(maxAmountInput.raw, parsedAmounts[Field.INPUT].raw)
: undefined
const maxAmountOutput =
!!userBalances[Field.OUTPUT] &&
TokensDeposited[Field.OUTPUT] &&
JSBI.greaterThan(
TokensDeposited[Field.OUTPUT]?.raw,
tokens[Field.OUTPUT].equals(WETH[chainId]) ? MIN_ETHER.raw : JSBI.BigInt(0)
)
? tokens[Field.OUTPUT].equals(WETH[chainId])
? TokensDeposited[Field.OUTPUT].subtract(MIN_ETHER)
: TokensDeposited[Field.OUTPUT]
: undefined : undefined
})
const atMaxAmountOutput = const [atMaxAmountInput, atMaxAmountOutput] = [Field.INPUT, Field.OUTPUT].map(index => {
!!maxAmountOutput && !!parsedAmounts[Field.OUTPUT] const field = Field[index]
? JSBI.equal(maxAmountOutput.raw, parsedAmounts[Field.OUTPUT].raw) const maxAmount = index === Field.INPUT ? maxAmountInput : maxAmountOutput
return !!maxAmount && !!parsedAmounts[Field[field]]
? JSBI.lessThanOrEqual(maxAmount.raw, parsedAmounts[Field[field]].raw)
: undefined : undefined
})
const maxAmountPoolToken = userLiquidity
const atMaxAmountPoolToken = const atMaxAmountPoolToken =
!!maxAmountOutput && !!parsedAmounts[Field.POOL] !!maxAmountOutput && !!parsedAmounts[Field.POOL]
? JSBI.equal(maxAmountPoolToken.raw, parsedAmounts[Field.POOL].raw) ? JSBI.lessThanOrEqual(maxAmountPoolToken.raw, parsedAmounts[Field.POOL].raw)
: undefined : undefined
// errors
const [generalError, setGeneralError] = useState()
const [inputError, setInputError] = useState()
const [outputError, setOutputError] = useState()
const [poolTokenError, setPoolTokenError] = useState()
const [isError, setIsError] = useState(false)
const [isValid, setIsValid] = useState(false)
// update errors live // update errors live
useEffect(() => { useEffect(() => {
// reset errors // reset errors
setGeneralError(false)
setInputError(null) setInputError(null)
setOutputError(null) setOutputError(null)
setPoolTokenError(null) setPoolTokenError(null)
setIsError(false) setIsError(false)
setIsValid(true)
if (!parsedAmounts[Field.INPUT]) {
setGeneralError('Enter an amount to continue')
setIsValid(false)
}
if (!parsedAmounts[Field.OUTPUT]) {
setGeneralError('Enter an amount to continue')
setIsValid(false)
}
if ( if (
totalPoolTokens && totalPoolTokens &&
userLiquidity && userLiquidity &&
...@@ -392,50 +423,44 @@ export default function RemoveLiquidity({ token0, token1 }) { ...@@ -392,50 +423,44 @@ export default function RemoveLiquidity({ token0, token1 }) {
) { ) {
setPoolTokenError('Input a liquidity amount less than or equal to your balance.') setPoolTokenError('Input a liquidity amount less than or equal to your balance.')
setIsError(true) setIsError(true)
setIsValid(false)
} }
if (parseFloat(parsedAmounts?.[Field.INPUT]?.toExact()) > parseFloat(userBalances?.[Field.INPUT]?.toExact())) { // if (
setInputError('Insufficient balance.') // parsedAmounts?.[Field.INPUT] &&
setIsError(true) // userBalances?.[Field.INPUT] &&
} // JSBI.greaterThan(parsedAmounts?.[Field.INPUT]?.raw, userBalances?.[Field.INPUT]?.raw)
if (parseFloat(parsedAmounts?.[Field.OUTPUT]?.toExact()) > parseFloat(userBalances?.[Field.OUTPUT]?.toExact())) { // ) {
setOutputError('Insufficient balance.') // setInputError('Insufficient balance.')
setIsError(true) // setIsError(true)
} // setIsValid(false)
}, [parsedAmounts, totalPoolTokens, userBalances, userLiquidity]) // }
// if (
// set error text based on all errors // parsedAmounts?.[Field.OUTPUT] &&
useEffect(() => { // userBalances?.[Field.OUTPUT] &&
setErrorText(null) // parseFloat(parsedAmounts?.[Field.OUTPUT]?.toExact()) > parseFloat(userBalances?.[Field.OUTPUT]?.toExact())
if (poolTokenError) { // ) {
setErrorText(poolTokenError) // setOutputError('Insufficient balance.')
} else if (!parsedAmounts[Field.INPUT]) { // setIsError(true)
setErrorText('Enter an amount to continue') // setIsValid(false)
} else if (outputError) { // }
setErrorText(outputError) }, [parsedAmounts, totalPoolTokens, userLiquidity])
} else if (inputError) {
setErrorText(inputError)
return
}
}, [inputError, outputError, parsedAmounts, poolTokenError])
// error state for button // error state for button
const isValid = !errorText
// state for txn // state for txn
const addTransaction = useTransactionAdder() const addTransaction = useTransactionAdder()
const [txHash, setTxHash] = useState() const [txHash, setTxHash] = useState()
// mock to set initial values either from URL or route from supply page
const routerAddress = '0xd9210Ff5A0780E083BB40e30d005d93a2DcFA4EF'
const router = getRouterContract(chainId, library, account)
const [sigInputs, setSigInputs] = useState([]) const [sigInputs, setSigInputs] = useState([])
const [signed, setSigned] = useState(false)
const [attemptedRemoval, setAttemptedRemoval] = useState(false)
const [deadline, setDeadline] = useState()
async function onSign() { async function onSign() {
const nonce = await exchangeContract.nonces(account) const nonce = await exchangeContract.nonces(account)
const deadline = 1739591241
const newDeadline = Math.ceil(Date.now() / 1000) + DEADLINE_FROM_NOW
setDeadline(newDeadline)
const EIP712Domain = [ const EIP712Domain = [
{ name: 'name', type: 'string' }, { name: 'name', type: 'string' },
...@@ -463,7 +488,7 @@ export default function RemoveLiquidity({ token0, token1 }) { ...@@ -463,7 +488,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
spender: routerAddress, spender: routerAddress,
value: parsedAmounts[Field.POOL].raw.toString(), value: parsedAmounts[Field.POOL].raw.toString(),
nonce: nonce.toHexString(), nonce: nonce.toHexString(),
deadline: deadline deadline: newDeadline
} }
const data = JSON.stringify({ const data = JSON.stringify({
types: { types: {
...@@ -478,58 +503,103 @@ export default function RemoveLiquidity({ token0, token1 }) { ...@@ -478,58 +503,103 @@ export default function RemoveLiquidity({ token0, token1 }) {
library.send('eth_signTypedData_v4', [account, data]).then(_signature => { library.send('eth_signTypedData_v4', [account, data]).then(_signature => {
const signature = splitSignature(_signature) const signature = splitSignature(_signature)
setSigInputs([signature.v, signature.r, signature.s]) setSigInputs([signature.v, signature.r, signature.s])
setSigned(true)
}) })
} }
async function onRemove() { async function onRemove() {
// now can structure txn setAttemptedRemoval(true)
const args = [ const router = getRouterContract(chainId, library, account)
tokens[Field.INPUT].address, let method, args, estimate
tokens[Field.OUTPUT].address,
parsedAmounts[Field.POOL].raw.toString(), // removal with ETH
parsedAmounts[Field.INPUT].raw.toString(), if (tokens[Field.INPUT] === WETH[chainId] || tokens[Field.OUTPUT] === WETH[chainId]) {
parsedAmounts[Field.OUTPUT].raw.toString(), method = router.removeLiquidityETHWithPermit
account, estimate = router.estimate.removeLiquidityETHWithPermit
1739591241, args = [
sigInputs[0], tokens[Field.OUTPUT] === WETH[chainId] ? tokens[Field.INPUT].address : tokens[Field.OUTPUT].address,
sigInputs[1], parsedAmounts[Field.POOL].raw.toString(),
sigInputs[2] tokens[Field.OUTPUT] === WETH[chainId]
] ? parsedAmounts[Field.INPUT].raw.toString()
: parsedAmounts[Field.OUTPUT].raw.toString(),
tokens[Field.OUTPUT] === WETH[chainId]
? parsedAmounts[Field.OUTPUT].raw.toString()
: parsedAmounts[Field.INPUT].raw.toString(),
account,
deadline,
sigInputs[0],
sigInputs[1],
sigInputs[2]
]
}
//removal without ETH
else {
method = router.removeLiquidityWithPermit
estimate = router.estimate.removeLiquidityWithPermit
args = [
tokens[Field.INPUT].address,
tokens[Field.OUTPUT].address,
parsedAmounts[Field.POOL].raw.toString(),
parsedAmounts[Field.INPUT].raw.toString(),
parsedAmounts[Field.OUTPUT].raw.toString(),
account,
deadline,
sigInputs[0],
sigInputs[1],
sigInputs[2]
]
}
const estimatedGasLimit = await router.estimate.removeLiquidityWithPermit(...args, { const estimatedGasLimit = await estimate(...args, {
value: ethers.constants.Zero value: ethers.constants.Zero
}).catch(() => {
resetModalState()
setShowConfirm(false)
}) })
const GAS_MARGIN = ethers.utils.bigNumberify(1000) method(...args, {
router gasLimit: calculateGasMargin(estimatedGasLimit, GAS_MARGIN)
.removeLiquidityWithPermit(...args, { })
gasLimit: calculateGasMargin(estimatedGasLimit, GAS_MARGIN)
})
.then(response => { .then(response => {
console.log('success') setPendingConfirmation(false)
setTxHash(response.hash) setTxHash(response.hash)
addTransaction(response.hash) addTransaction(response)
}) })
.catch(e => { .catch(() => {
console.log('error trying txn') resetModalState()
setShowConfirm(false)
}) })
} }
function resetModalState() {
setSigned(false)
setSigInputs(null)
setAttemptedRemoval(false)
setPendingConfirmation(true)
}
// show advanced mode or not
const [showAdvanced, setShowAdvanced] = useState(false)
return ( return (
<> <Wrapper>
<ConfirmationModal <ConfirmationModal
isOpen={showConfirm} isOpen={showConfirm}
onDismiss={() => { onDismiss={() => {
toggleConfirm(false) resetModalState()
setShowConfirm(false)
}} }}
amount0={parsedAmounts[Field.INPUT]} amount0={parsedAmounts[Field.INPUT]}
amount1={parsedAmounts[Field.OUTPUT]} amount1={parsedAmounts[Field.OUTPUT]}
price={route?.midPrice} price={route?.midPrice}
liquidityAmount={parsedAmounts[Field.POOL]} liquidityAmount={parsedAmounts[Field.POOL]}
transactionType={TRANSACTION_TYPE.REMOVE} transactionType={TRANSACTION_TYPE.REMOVE}
contractCall={() => {}} contractCall={onRemove}
extraCall={onSign}
signed={signed}
attemptedRemoval={attemptedRemoval}
pendingConfirmation={pendingConfirmation} pendingConfirmation={pendingConfirmation}
hash={''} hash={txHash ? txHash : ''}
/> />
<SearchModal <SearchModal
isOpen={showSearch} isOpen={showSearch}
...@@ -554,119 +624,156 @@ export default function RemoveLiquidity({ token0, token1 }) { ...@@ -554,119 +624,156 @@ export default function RemoveLiquidity({ token0, token1 }) {
<ChevronDown size={24} /> <ChevronDown size={24} />
</RowBetween> </RowBetween>
</ButtonEmpty> </ButtonEmpty>
<CurrencyInputPanel
field={Field.POOL}
value={formattedAmounts[Field.POOL]}
onUserInput={onUserInput}
onMax={() => {
maxAmountPoolToken && onMaxPool(maxAmountPoolToken.toExact())
}}
atMax={atMaxAmountPoolToken}
onTokenSelection={onTokenSelection}
title={'Deposit'}
error={poolTokenError}
disableTokenSelect
token={exchange?.liquidityToken}
isExchange={true}
exchange={exchange}
/>
<ColumnCenter>
<ArrowDown size="16" color="#888D9B" />
</ColumnCenter>
<CurrencyInputPanel
field={Field.INPUT}
value={formattedAmounts[Field.INPUT]}
onUserInput={onUserInput}
onMax={() => {
maxAmountInput && onMaxInput(maxAmountInput.toExact())
}}
atMax={atMaxAmountInput}
token={tokens[Field.INPUT]}
onTokenSelection={onTokenSelection}
title={'Deposit'}
error={inputError}
disableTokenSelect
customBalance={TokensDeposited[Field.INPUT]}
/>
<ColumnCenter>
<Plus size="16" color="#888D9B" />
</ColumnCenter>
<CurrencyInputPanel
field={Field.OUTPUT}
value={formattedAmounts[Field.OUTPUT]}
onUserInput={onUserInput}
onMax={() => {
maxAmountOutput && onMaxOutput(maxAmountOutput.toExact())
}}
atMax={atMaxAmountOutput}
token={tokens[Field.OUTPUT]}
onTokenSelection={onTokenSelection}
title={'Deposit'}
error={outputError}
disableTokenSelect
customBalance={TokensDeposited[Field.OUTPUT]}
/>
<ColumnCenter>
<ArrowDown size="16" color="#888D9B" />
</ColumnCenter>
<LightCard> <LightCard>
<AutoColumn gap="10px"> <AutoColumn gap="20px">
<RowBetween>
Pool Tokens Burned:
<div>{formattedAmounts[Field.POOL] ? formattedAmounts[Field.POOL] : '-'}</div>
</RowBetween>
<RowBetween> <RowBetween>
{exchange?.token0.symbol} Removed: <Text fontWeight={500}>Amount To Remove</Text>
<div>{formattedAmounts[Field.INPUT] ? formattedAmounts[Field.INPUT] : '-'}</div> <ClickableText
fontWeight={500}
onClick={() => {
setShowAdvanced(!showAdvanced)
}}
color="#2172E5"
>
{showAdvanced ? 'Minimal' : 'Advanced'}
</ClickableText>
</RowBetween> </RowBetween>
<RowBetween>
{exchange?.token1.symbol} Removed: <RowBetween style={{ alignItems: 'flex-end' }}>
<div>{formattedAmounts[Field.OUTPUT] ? formattedAmounts[Field.OUTPUT] : '-'}</div> <Text fontSize={72} fontWeight={500}>
</RowBetween> {percentageAmount}%
<RowBetween> </Text>
Rate: <MaxButton
<div> onClick={() => {
1 {exchange?.token0.symbol} ={' '} setPercentageAmount(100)
{independentField === Field.INPUT || independentField === Field.POOL }}
? route?.midPrice.toSignificant(6) >
: route?.midPrice.invert().toSignificant(6)}{' '} Max
{exchange?.token1.symbol} </MaxButton>
</div>
</RowBetween> </RowBetween>
<Slider value={percentageAmount} onChange={handleSliderChange} />
</AutoColumn> </AutoColumn>
</LightCard> </LightCard>
<ColumnCenter style={{ height: '20px' }}> {!showAdvanced && (
<ErrorText fontSize={12} error={isError}> <>
{errorText && errorText} <ColumnCenter>
</ErrorText> <ArrowDown size="16" color="#888D9B" />
</ColumnCenter> </ColumnCenter>{' '}
<LightCard>
<AutoColumn gap="10px">
<RowBetween>
<Text fontSize={24} fontWeight={500}>
{formattedAmounts[Field.INPUT]}
</Text>
<RowFixed>
<TokenLogo address={tokens[Field.INPUT]?.address || ''} style={{ marginRight: '12px' }} />
<Text fontSize={24} fontWeight={500}>
{tokens[Field.INPUT]?.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowBetween>
<Text fontSize={24} fontWeight={500}>
{formattedAmounts[Field.OUTPUT]}
</Text>
<RowFixed>
<TokenLogo address={tokens[Field.OUTPUT]?.address || ''} style={{ marginRight: '12px' }} />
<Text fontSize={24} fontWeight={500}>
{tokens[Field.OUTPUT]?.symbol}
</Text>
</RowFixed>
</RowBetween>
</AutoColumn>
</LightCard>
</>
)}
{showAdvanced && (
<>
<CurrencyInputPanel
field={Field.POOL}
value={formattedAmounts[Field.POOL]}
onUserInput={onUserInput}
onMax={() => {
maxAmountPoolToken && onMax(maxAmountPoolToken.toExact(), Field.POOL)
}}
atMax={atMaxAmountPoolToken}
onTokenSelection={onTokenSelection}
title={'Deposit'}
error={poolTokenError}
disableTokenSelect
token={exchange?.liquidityToken}
isExchange={true}
exchange={exchange}
/>
<ColumnCenter>
<ArrowDown size="16" color="#888D9B" />
</ColumnCenter>
<CurrencyInputPanel
field={Field.INPUT}
value={formattedAmounts[Field.INPUT]}
onUserInput={onUserInput}
onMax={() => {
maxAmountInput && onMax(maxAmountInput.toExact(), Field.INPUT)
}}
atMax={atMaxAmountInput}
token={tokens[Field.INPUT]}
onTokenSelection={onTokenSelection}
title={'Deposit'}
error={inputError}
disableTokenSelect
customBalance={TokensDeposited[Field.INPUT]}
/>
<ColumnCenter>
<Plus size="16" color="#888D9B" />
</ColumnCenter>
<CurrencyInputPanel
field={Field.OUTPUT}
value={formattedAmounts[Field.OUTPUT]}
onUserInput={onUserInput}
onMax={() => {
maxAmountOutput && onMax(maxAmountOutput.toExact(), Field.OUTPUT)
}}
atMax={atMaxAmountOutput}
token={tokens[Field.OUTPUT]}
onTokenSelection={onTokenSelection}
title={'Deposit'}
error={outputError}
disableTokenSelect
customBalance={TokensDeposited[Field.OUTPUT]}
/>
</>
)}
<RowBetween> <RowBetween>
<ButtonPrimary Rate:
onClick={() => { <div>
// toggleConfirm(true) 1 {exchange?.token0.symbol} ={' '}
onSign() {independentField === Field.INPUT || independentField === Field.POOL
}} ? route?.midPrice.toSignificant(6)
width="48%" : route?.midPrice.invert().toSignificant(6)}{' '}
disabled={!isValid} {exchange?.token1.symbol}
> </div>
<Text fontSize={20} fontWeight={500}>
Sign
</Text>
</ButtonPrimary>
<ButtonPrimary
onClick={() => {
// toggleConfirm(true)
onRemove()
}}
width="48%"
disabled={!isValid}
>
<Text fontSize={20} fontWeight={500}>
Remove
</Text>
</ButtonPrimary>
</RowBetween> </RowBetween>
<ButtonPrimary
onClick={() => {
setShowConfirm(true)
}}
disabled={!isValid}
>
<Text fontSize={20} fontWeight={500}>
Remove
</Text>
</ButtonPrimary>
<FixedBottom>
<PositionCard
exchangeAddress={exchange.liquidityToken.address}
token0={exchange.token0}
token1={exchange.token1}
minimal={true}
/>
</FixedBottom>
</AutoColumn> </AutoColumn>
</> </Wrapper>
) )
} }
import React, { useEffect, useState, useCallback } from 'react' import React, { useState } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { TokenAmount, JSBI, Percent, Token } from '@uniswap/sdk' import { JSBI } from '@uniswap/sdk'
import ReactGA from 'react-ga'
import { withRouter } from 'react-router-dom' import { useWeb3React } from '@web3-react/core'
import { useAllTokens } from '../../contexts/Tokens'
import { useAllExchanges } from '../../contexts/Exchanges'
import { useAllBalances, useAccountLPBalances } from '../../contexts/Balances'
import { Link } from '../../theme'
import { AutoColumn } from '../../components/Column'
import Row, { RowBetween, RowFixed } from '../../components/Row'
import { ButtonDropwdown, ButtonSecondary } from '../../components/Button'
import Card from '../../components/Card' import Card from '../../components/Card'
import { Text } from 'rebass'
import DoubleLogo from '../../components/DoubleLogo'
import SearchModal from '../../components/SearchModal' import SearchModal from '../../components/SearchModal'
import PositionCard from '../../components/PositionCard'
import Row, { RowBetween } from '../../components/Row'
import { Link } from '../../theme'
import { Text } from 'rebass'
import { AutoColumn } from '../../components/Column'
import { ArrowRight } from 'react-feather' import { ArrowRight } from 'react-feather'
import { ButtonDropwdown } from '../../components/Button'
import { useAllExchanges } from '../../contexts/Exchanges'
import { useAllBalances, useAccountLPBalances } from '../../contexts/Balances'
import { useWeb3React } from '@web3-react/core'
import { useAllTokens } from '../../contexts/Tokens'
import { useExchangeContract } from '../../hooks'
import TokenLogo from '../../components/TokenLogo'
const Positions = styled.div` const Positions = styled.div`
position: relative; position: relative;
margin-top: 38px; margin-top: 38px;
` `
const FixedBottom = styled.div` const FixedBottom = styled.div`
position: absolute; position: absolute;
bottom: -240px; bottom: -240px;
width: 100%; width: 100%;
` `
function ExchangeCard({ exchangeAddress, token0, token1, history, allBalances }) { export default function Supply() {
const { account, chainId } = useWeb3React()
const userPoolBalance = allBalances?.[account]?.[exchangeAddress]
const [totalPoolTokens, setTotalPoolTokens] = useState()
// get the total pool token supply
const exchangeContract = useExchangeContract(exchangeAddress)
const fetchPoolTokens = useCallback(() => {
if (exchangeContract) {
exchangeContract.totalSupply().then(totalSupply => {
if (totalSupply !== undefined) {
const supplyFormatted = JSBI.BigInt(totalSupply)
const tokenSupplyFormatted = new TokenAmount(new Token(chainId, exchangeAddress, 18), supplyFormatted)
setTotalPoolTokens(tokenSupplyFormatted)
}
})
}
}, [exchangeContract, chainId, exchangeAddress])
useEffect(() => {
fetchPoolTokens()
}, [fetchPoolTokens])
const poolTokenPercentage =
!!userPoolBalance && !!totalPoolTokens ? new Percent(userPoolBalance.raw, totalPoolTokens.raw) : undefined
const token0Deposited = poolTokenPercentage?.multiply(allBalances[exchangeAddress][token0.address])
const token1Deposited = poolTokenPercentage?.multiply(allBalances[exchangeAddress][token1.address])
return (
<Card border="1px solid #EDEEF2">
<AutoColumn gap="20px">
<RowBetween>
<RowFixed>
<DoubleLogo a0={token0?.address || ''} a1={token1?.address || ''} margin={true} size={24} />
<Text fontWeight={500} fontSize={20}>
{token0?.symbol}:{token1?.symbol}
</Text>
</RowFixed>
<Text fontWeight={500} fontSize={20}>
{userPoolBalance ? userPoolBalance.toFixed(6) : '-'}
</Text>
</RowBetween>
<AutoColumn gap="12px">
<RowBetween>
<Text color="#888D9B" fontSize={16} fontWeight={500}>
{token0?.symbol} Deposited:
</Text>
{token0Deposited ? (
<RowFixed>
<TokenLogo address={token0?.address || ''} />
<Text color="#888D9B" fontSize={16} fontWeight={500}>
{token0Deposited?.toSignificant(6)}
</Text>
</RowFixed>
) : (
'-'
)}
</RowBetween>
<RowBetween>
<Text color="#888D9B" fontSize={16} fontWeight={500}>
{token1?.symbol} Deposited:
</Text>
{token1Deposited ? (
<RowFixed>
<TokenLogo address={token1.address || ''} />
<Text color="#888D9B" fontSize={16} fontWeight={500}>
{token1Deposited?.toSignificant(6)}
</Text>
</RowFixed>
) : (
'-'
)}
</RowBetween>
<RowBetween>
<Text color="#888D9B" fontSize={16} fontWeight={500}>
Your pool share:
</Text>
<Text color="#888D9B" fontSize={16} fontWeight={500}>
{poolTokenPercentage ? poolTokenPercentage.toFixed(2) + '%' : '-'}
</Text>
</RowBetween>
</AutoColumn>
<RowBetween>
<ButtonSecondary
width="48%"
onClick={() => {
history.push('/add/' + token0?.address + '-' + token1?.address)
}}
>
Add
</ButtonSecondary>
<ButtonSecondary
width="48%"
onClick={() => {
history.push('/remove/' + token0?.address + '-' + token1?.address)
}}
>
Remove
</ButtonSecondary>
</RowBetween>
</AutoColumn>
</Card>
)
}
function Supply({ history }) {
useEffect(() => {
ReactGA.pageview(window.location.pathname + window.location.search)
}, [])
const { account } = useWeb3React() const { account } = useWeb3React()
const [showSearch, setShowSearch] = useState(false)
const [showSearch, toggleSearch] = useState(false)
const exchanges = useAllExchanges()
const allTokens = useAllTokens() const allTokens = useAllTokens()
const allBalances = useAllBalances() const allBalances = useAllBalances()
const allExchanges = useAllExchanges()
// initiate listener for LP balances // initiate listener for LP balances
useAccountLPBalances(account) useAccountLPBalances(account)
const filteredPairList = Object.keys(exchanges).map((exchangeAddress, i) => { const filteredExchangeList = Object.keys(allExchanges).map((exchangeAddress, i) => {
const exchange = exchanges[exchangeAddress] const balance = allBalances?.[account]?.[exchangeAddress]
// gate on positive address return (
if (allBalances?.[account]?.[exchangeAddress]) { balance &&
const token0 = allTokens[exchange.token0] JSBI.greaterThan(balance.raw, JSBI.BigInt(0)) && (
const token1 = allTokens[exchange.token1] <PositionCard
return (
<ExchangeCard
history={history}
key={i} key={i}
exchangeAddress={exchangeAddress} exchangeAddress={exchangeAddress}
token0={token0} token0={allTokens[allExchanges[exchangeAddress].token0]}
token1={token1} token1={allTokens[allExchanges[exchangeAddress].token1]}
allBalances={allBalances}
/> />
) )
} else { )
return ''
}
}) })
return ( return (
<> <>
<ButtonDropwdown <ButtonDropwdown
onClick={() => { onClick={() => {
toggleSearch(true) setShowSearch(true)
}} }}
> >
<Text fontSize={20}>Find a pool</Text> <Text fontSize={20}>Find a pool</Text>
...@@ -193,7 +70,7 @@ function Supply({ history }) { ...@@ -193,7 +70,7 @@ function Supply({ history }) {
<Text fontWeight={500}>View on Uniswap.info</Text> <Text fontWeight={500}>View on Uniswap.info</Text>
</Link> </Link>
</RowBetween> </RowBetween>
{filteredPairList} {filteredExchangeList}
</AutoColumn> </AutoColumn>
<FixedBottom> <FixedBottom>
<Card bg="rgba(255, 255, 255, 0.6)" padding={'24px'}> <Card bg="rgba(255, 255, 255, 0.6)" padding={'24px'}>
...@@ -216,11 +93,9 @@ function Supply({ history }) { ...@@ -216,11 +93,9 @@ function Supply({ history }) {
<SearchModal <SearchModal
isOpen={showSearch} isOpen={showSearch}
onDismiss={() => { onDismiss={() => {
toggleSearch(false) setShowSearch(false)
}} }}
/> />
</> </>
) )
} }
export default withRouter(Supply)
...@@ -6,15 +6,7 @@ import ROUTER_ABI from '../constants/abis/router' ...@@ -6,15 +6,7 @@ import ROUTER_ABI from '../constants/abis/router'
import ERC20_ABI from '../constants/abis/erc20' 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, SUPPORTED_THEMES } from '../constants' import { FACTORY_ADDRESSES, SUPPORTED_THEMES } from '../constants'
import { import { bigNumberify, keccak256, defaultAbiCoder, toUtf8Bytes, solidityPack } from 'ethers/utils'
BigNumber,
bigNumberify,
getAddress,
keccak256,
defaultAbiCoder,
toUtf8Bytes,
solidityPack
} from 'ethers/utils'
import UncheckedJsonRpcSigner from './signer' import UncheckedJsonRpcSigner from './signer'
......
...@@ -1039,6 +1039,13 @@ ...@@ -1039,6 +1039,13 @@
dependencies: dependencies:
regenerator-runtime "^0.13.2" regenerator-runtime "^0.13.2"
"@babel/runtime@^7.4.4", "@babel/runtime@^7.6.2":
version "7.8.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.7.tgz#8fefce9802db54881ba59f90bb28719b4996324d"
integrity sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@^7.4.0", "@babel/template@^7.7.4": "@babel/template@^7.4.0", "@babel/template@^7.7.4":
version "7.7.4" version "7.7.4"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.7.4.tgz#428a7d9eecffe27deac0a98e23bf8e3675d2a77b" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.7.4.tgz#428a7d9eecffe27deac0a98e23bf8e3675d2a77b"
...@@ -1121,7 +1128,7 @@ ...@@ -1121,7 +1128,7 @@
"@emotion/utils" "0.11.3" "@emotion/utils" "0.11.3"
babel-plugin-emotion "^10.0.27" babel-plugin-emotion "^10.0.27"
"@emotion/hash@0.7.4": "@emotion/hash@0.7.4", "@emotion/hash@^0.7.4":
version "0.7.4" version "0.7.4"
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.7.4.tgz#f14932887422c9056b15a8d222a9074a7dfa2831" resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.7.4.tgz#f14932887422c9056b15a8d222a9074a7dfa2831"
integrity sha512-fxfMSBMX3tlIbKUdtGKxqB1fyrH6gVrX39Gsv3y8lRYKUqlgDt3UMqQyGnR1bQMa2B8aGnhLZokZgg8vT0Le+A== integrity sha512-fxfMSBMX3tlIbKUdtGKxqB1fyrH6gVrX39Gsv3y8lRYKUqlgDt3UMqQyGnR1bQMa2B8aGnhLZokZgg8vT0Le+A==
...@@ -1697,6 +1704,69 @@ ...@@ -1697,6 +1704,69 @@
resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-4.72.0.tgz#43df23af013ad1135407e5cf33ca6e4c4c7708d5" resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-4.72.0.tgz#43df23af013ad1135407e5cf33ca6e4c4c7708d5"
integrity sha512-o+TYF8vBcyySRsb2kqBDv/KMeme8a2nwWoG+lAWzbDmWfb2/MrVWYCVYDYvjXdSoI/Cujqy1i0gIDrkdxa9chA== integrity sha512-o+TYF8vBcyySRsb2kqBDv/KMeme8a2nwWoG+lAWzbDmWfb2/MrVWYCVYDYvjXdSoI/Cujqy1i0gIDrkdxa9chA==
"@material-ui/core@^4.9.5":
version "4.9.5"
resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.9.5.tgz#384869f2840b243241f7881a902f5ffc48360830"
integrity sha512-hVuUqw6847jcgRsUqzCiYCXcIJYhPUfM3gS9sNehTsbI0SF3tufLNO2B2Cgkuns8uOGy0nicD4p3L7JqhnEElg==
dependencies:
"@babel/runtime" "^7.4.4"
"@material-ui/styles" "^4.9.0"
"@material-ui/system" "^4.9.3"
"@material-ui/types" "^5.0.0"
"@material-ui/utils" "^4.7.1"
"@types/react-transition-group" "^4.2.0"
clsx "^1.0.2"
hoist-non-react-statics "^3.3.2"
popper.js "^1.14.1"
prop-types "^15.7.2"
react-is "^16.8.0"
react-transition-group "^4.3.0"
"@material-ui/styles@^4.9.0":
version "4.9.0"
resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.9.0.tgz#10c31859f6868cfa9d3adf6b6c3e32c9d676bc76"
integrity sha512-nJHum4RqYBPWsjL/9JET8Z02FZ9gSizlg/7LWVFpIthNzpK6OQ5OSRR4T4x9/p+wK3t1qNn3b1uI4XpnZaPxOA==
dependencies:
"@babel/runtime" "^7.4.4"
"@emotion/hash" "^0.7.4"
"@material-ui/types" "^5.0.0"
"@material-ui/utils" "^4.7.1"
clsx "^1.0.2"
csstype "^2.5.2"
hoist-non-react-statics "^3.2.1"
jss "^10.0.3"
jss-plugin-camel-case "^10.0.3"
jss-plugin-default-unit "^10.0.3"
jss-plugin-global "^10.0.3"
jss-plugin-nested "^10.0.3"
jss-plugin-props-sort "^10.0.3"
jss-plugin-rule-value-function "^10.0.3"
jss-plugin-vendor-prefixer "^10.0.3"
prop-types "^15.7.2"
"@material-ui/system@^4.9.3":
version "4.9.3"
resolved "https://registry.yarnpkg.com/@material-ui/system/-/system-4.9.3.tgz#ee48990d7941237fdaf21b7b399981d614bb0875"
integrity sha512-DBGsTKYrLlFpHG8BUp0X6ZpvaOzef+GhSwn/8DwVTXUdHitphaPQoL9xucrI8X9MTBo//El+7nylko7lo7eJIw==
dependencies:
"@babel/runtime" "^7.4.4"
"@material-ui/utils" "^4.7.1"
prop-types "^15.7.2"
"@material-ui/types@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@material-ui/types/-/types-5.0.0.tgz#26d6259dc6b39f4c2e1e9aceff7a11e031941741"
integrity sha512-UeH2BuKkwDndtMSS0qgx1kCzSMw+ydtj0xx/XbFtxNSTlXydKwzs5gVW5ZKsFlAkwoOOQ9TIsyoCC8hq18tOwg==
"@material-ui/utils@^4.7.1":
version "4.7.1"
resolved "https://registry.yarnpkg.com/@material-ui/utils/-/utils-4.7.1.tgz#dc16c7f0d2cd02fbcdd5cfe601fd6863ae3cc652"
integrity sha512-+ux0SlLdlehvzCk2zdQ3KiS3/ylWvuo/JwAGhvb8dFVvwR21K28z0PU9OQW2PGogrMEdvX3miEI5tGxTwwWiwQ==
dependencies:
"@babel/runtime" "^7.4.4"
prop-types "^15.7.2"
react-is "^16.8.0"
"@mrmlnc/readdir-enhanced@^2.2.1": "@mrmlnc/readdir-enhanced@^2.2.1":
version "2.2.1" version "2.2.1"
resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
...@@ -2198,6 +2268,13 @@ ...@@ -2198,6 +2268,13 @@
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react-transition-group@^4.2.0":
version "4.2.4"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.2.4.tgz#c7416225987ccdb719262766c1483da8f826838d"
integrity sha512-8DMUaDqh0S70TjkqU0DxOu80tFUiiaS9rxkWip/nb7gtvAsbqOXm02UCmR8zdcjWujgeYPiPNTVpVpKzUDotwA==
dependencies:
"@types/react" "*"
"@types/react@*": "@types/react@*":
version "16.9.17" version "16.9.17"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.17.tgz#58f0cc0e9ec2425d1441dd7b623421a867aa253e" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.17.tgz#58f0cc0e9ec2425d1441dd7b623421a867aa253e"
...@@ -4670,6 +4747,11 @@ cloneable-readable@^1.0.0: ...@@ -4670,6 +4747,11 @@ cloneable-readable@^1.0.0:
process-nextick-args "^2.0.0" process-nextick-args "^2.0.0"
readable-stream "^2.3.5" readable-stream "^2.3.5"
clsx@^1.0.2:
version "1.1.0"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.0.tgz#62937c6adfea771247c34b54d320fb99624f5702"
integrity sha512-3avwM37fSK5oP6M5rQ9CNe99lwxhXDOeSWVPAOYF6OazUTgZCMb0yWlJpmdD74REy1gkEaFiub2ULv4fq9GUhA==
co@^4.6.0: co@^4.6.0:
version "4.6.0" version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
...@@ -5199,6 +5281,14 @@ css-unit-converter@^1.1.1: ...@@ -5199,6 +5281,14 @@ css-unit-converter@^1.1.1:
resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.1.tgz#d9b9281adcfd8ced935bdbaba83786897f64e996" resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.1.tgz#d9b9281adcfd8ced935bdbaba83786897f64e996"
integrity sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY= integrity sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY=
css-vendor@^2.0.7:
version "2.0.7"
resolved "https://registry.yarnpkg.com/css-vendor/-/css-vendor-2.0.7.tgz#4e6d53d953c187981576d6a542acc9fb57174bda"
integrity sha512-VS9Rjt79+p7M0WkPqcAza4Yq1ZHrsHrwf7hPL/bjQB+c1lwmAI+1FXxYTYt818D/50fFVflw0XKleiBN5RITkg==
dependencies:
"@babel/runtime" "^7.6.2"
is-in-browser "^1.0.2"
css-what@2.1: css-what@2.1:
version "2.1.3" version "2.1.3"
resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2"
...@@ -5326,6 +5416,11 @@ csstype@^2.2.0, csstype@^2.5.7: ...@@ -5326,6 +5416,11 @@ csstype@^2.2.0, csstype@^2.5.7:
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.8.tgz#0fb6fc2417ffd2816a418c9336da74d7f07db431" resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.8.tgz#0fb6fc2417ffd2816a418c9336da74d7f07db431"
integrity sha512-msVS9qTuMT5zwAGCVm4mxfrZ18BNc6Csd0oJAtiFMZ1FAx1CCvy2+5MDmYoix63LM/6NDbNtodCiGYGmFgO0dA== integrity sha512-msVS9qTuMT5zwAGCVm4mxfrZ18BNc6Csd0oJAtiFMZ1FAx1CCvy2+5MDmYoix63LM/6NDbNtodCiGYGmFgO0dA==
csstype@^2.5.2, csstype@^2.6.5, csstype@^2.6.7:
version "2.6.9"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098"
integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q==
cyclist@^1.0.1: cyclist@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
...@@ -5723,6 +5818,14 @@ dom-converter@^0.2: ...@@ -5723,6 +5818,14 @@ dom-converter@^0.2:
dependencies: dependencies:
utila "~0.4" utila "~0.4"
dom-helpers@^5.0.1:
version "5.1.3"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.1.3.tgz#7233248eb3a2d1f74aafca31e52c5299cc8ce821"
integrity sha512-nZD1OtwfWGRBWlpANxacBEZrEuLa16o1nh7YopFWeoF68Zt8GGEmzHu6Xv4F3XaFIC+YXtTLrzgqKxFgLEe4jw==
dependencies:
"@babel/runtime" "^7.6.3"
csstype "^2.6.7"
dom-serializer@0: dom-serializer@0:
version "0.2.2" version "0.2.2"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
...@@ -8046,6 +8149,13 @@ hoist-non-react-statics@^3.1.0: ...@@ -8046,6 +8149,13 @@ hoist-non-react-statics@^3.1.0:
dependencies: dependencies:
react-is "^16.7.0" react-is "^16.7.0"
hoist-non-react-statics@^3.2.1, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
dependencies:
react-is "^16.7.0"
home-or-tmp@^2.0.0: home-or-tmp@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
...@@ -8232,6 +8342,11 @@ https-browserify@^1.0.0: ...@@ -8232,6 +8342,11 @@ https-browserify@^1.0.0:
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
hyphenate-style-name@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz#097bb7fa0b8f1a9cf0bd5c734cf95899981a9b48"
integrity sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ==
i18next-browser-languagedetector@^3.0.1: i18next-browser-languagedetector@^3.0.1:
version "3.1.1" version "3.1.1"
resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-3.1.1.tgz#1a0c236d4339476cc3632da60ff947bbc2e3ba3c" resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-3.1.1.tgz#1a0c236d4339476cc3632da60ff947bbc2e3ba3c"
...@@ -8686,6 +8801,11 @@ is-hex-prefixed@1.0.0: ...@@ -8686,6 +8801,11 @@ is-hex-prefixed@1.0.0:
resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554" resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554"
integrity sha1-fY035q135dEnFIkTxXPggtd39VQ= integrity sha1-fY035q135dEnFIkTxXPggtd39VQ=
is-in-browser@^1.0.2, is-in-browser@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835"
integrity sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=
is-natural-number@^4.0.1: is-natural-number@^4.0.1:
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8"
...@@ -9596,6 +9716,75 @@ jsprim@^1.2.2: ...@@ -9596,6 +9716,75 @@ jsprim@^1.2.2:
json-schema "0.2.3" json-schema "0.2.3"
verror "1.10.0" verror "1.10.0"
jss-plugin-camel-case@^10.0.3:
version "10.0.4"
resolved "https://registry.yarnpkg.com/jss-plugin-camel-case/-/jss-plugin-camel-case-10.0.4.tgz#3dedecec1e5bba0bf6141c2c05e2ab11ea4b468d"
integrity sha512-+wnqxJsyfUnOn0LxVg3GgZBSjfBCrjxwx7LFxwVTUih0ceGaXKZoieheNOaTo5EM4w8bt1nbb8XonpQCj67C6A==
dependencies:
"@babel/runtime" "^7.3.1"
hyphenate-style-name "^1.0.3"
jss "10.0.4"
jss-plugin-default-unit@^10.0.3:
version "10.0.4"
resolved "https://registry.yarnpkg.com/jss-plugin-default-unit/-/jss-plugin-default-unit-10.0.4.tgz#df03885de20f20a1fc1c21bdb7c62e865ee400d9"
integrity sha512-T0mhL/Ogp/quvod/jAHEqKvptLDxq7Cj3a+7zRuqK8HxUYkftptN89wJElZC3rshhNKiogkEYhCWenpJdFvTBg==
dependencies:
"@babel/runtime" "^7.3.1"
jss "10.0.4"
jss-plugin-global@^10.0.3:
version "10.0.4"
resolved "https://registry.yarnpkg.com/jss-plugin-global/-/jss-plugin-global-10.0.4.tgz#412245b56133cc88bec654a70d82d5922619f4c5"
integrity sha512-N8n9/GHENZce+sqE4UYiZiJtI+t+erT/BypHOrNYAfIoNEj7OYsOEKfIo2P0GpLB3QyDAYf5eo9XNdZ8veEkUA==
dependencies:
"@babel/runtime" "^7.3.1"
jss "10.0.4"
jss-plugin-nested@^10.0.3:
version "10.0.4"
resolved "https://registry.yarnpkg.com/jss-plugin-nested/-/jss-plugin-nested-10.0.4.tgz#4d15ad13995fb6e4125618006473a096d2475d75"
integrity sha512-QM21BKVt8LDeoRfowvAMh/s+/89VYrreIIE6ch4pvw0oAXDWw1iorUPlqLZ7uCO3UL0uFtQhJq3QMLN6Lr1v0A==
dependencies:
"@babel/runtime" "^7.3.1"
jss "10.0.4"
tiny-warning "^1.0.2"
jss-plugin-props-sort@^10.0.3:
version "10.0.4"
resolved "https://registry.yarnpkg.com/jss-plugin-props-sort/-/jss-plugin-props-sort-10.0.4.tgz#43c880ff8dfcf858f809f663ece5e65a1d945b5a"
integrity sha512-WoETdOCjGskuin/OMt2uEdDPLZF3vfQuHXF+XUHGJrq0BAapoyGQDcv37SeReDlkRAbVXkEZPsIMvYrgHSHFiA==
dependencies:
"@babel/runtime" "^7.3.1"
jss "10.0.4"
jss-plugin-rule-value-function@^10.0.3:
version "10.0.4"
resolved "https://registry.yarnpkg.com/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.0.4.tgz#2f4cf4a86ad3eba875bb48cb9f4a7ed35cb354e7"
integrity sha512-0hrzOSWRF5ABJGaHrlnHbYZjU877Ofzfh2id3uLtBvemGQLHI+ldoL8/+6iPSRa7M8z8Ngfg2vfYhKjUA5gA0g==
dependencies:
"@babel/runtime" "^7.3.1"
jss "10.0.4"
jss-plugin-vendor-prefixer@^10.0.3:
version "10.0.4"
resolved "https://registry.yarnpkg.com/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.0.4.tgz#1626ef612a4541cff17cf96815e1740155214ed2"
integrity sha512-4JgEbcrdeMda1qvxTm1CnxFJAWVV++VLpP46HNTrfH7VhVlvUpihnUNs2gAlKuRT/XSBuiWeLAkrTqF4NVrPig==
dependencies:
"@babel/runtime" "^7.3.1"
css-vendor "^2.0.7"
jss "10.0.4"
jss@10.0.4, jss@^10.0.3:
version "10.0.4"
resolved "https://registry.yarnpkg.com/jss/-/jss-10.0.4.tgz#46ebdde1c40c9a079d64f3334cb88ae28fd90bfd"
integrity sha512-GqHmeDK83qbqMAVjxyPfN1qJVTKZne533a9bdCrllZukUM8npG/k+JumEPI86IIB5ifaZAHG2HAsUziyxOiooQ==
dependencies:
"@babel/runtime" "^7.3.1"
csstype "^2.6.5"
is-in-browser "^1.1.3"
tiny-warning "^1.0.2"
jsx-ast-utils@^2.2.1: jsx-ast-utils@^2.2.1:
version "2.2.3" version "2.2.3"
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz#8a9364e402448a3ce7f14d357738310d9248054f" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz#8a9364e402448a3ce7f14d357738310d9248054f"
...@@ -11563,6 +11752,11 @@ popper.js@1.14.3: ...@@ -11563,6 +11752,11 @@ popper.js@1.14.3:
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.3.tgz#1438f98d046acf7b4d78cd502bf418ac64d4f095" resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.3.tgz#1438f98d046acf7b4d78cd502bf418ac64d4f095"
integrity sha1-FDj5jQRqz3tNeM1QK/QYrGTU8JU= integrity sha1-FDj5jQRqz3tNeM1QK/QYrGTU8JU=
popper.js@^1.14.1:
version "1.16.1"
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b"
integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==
portfinder@^1.0.25: portfinder@^1.0.25:
version "1.0.25" version "1.0.25"
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.25.tgz#254fd337ffba869f4b9d37edc298059cb4d35eca" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.25.tgz#254fd337ffba869f4b9d37edc298059cb4d35eca"
...@@ -12746,6 +12940,11 @@ react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-i ...@@ -12746,6 +12940,11 @@ react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-i
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c"
integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q== integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==
react-is@^16.8.0:
version "16.13.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.0.tgz#0f37c3613c34fe6b37cd7f763a0d6293ab15c527"
integrity sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA==
react-remove-scroll-bar@^1.1.5: react-remove-scroll-bar@^1.1.5:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-1.2.0.tgz#07250b2bc581f56315759c454c9b159dd04ba49d" resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-1.2.0.tgz#07250b2bc581f56315759c454c9b159dd04ba49d"
...@@ -12874,6 +13073,16 @@ react-switch@^5.0.1: ...@@ -12874,6 +13073,16 @@ react-switch@^5.0.1:
dependencies: dependencies:
prop-types "^15.6.2" prop-types "^15.6.2"
react-transition-group@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.3.0.tgz#fea832e386cf8796c58b61874a3319704f5ce683"
integrity sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw==
dependencies:
"@babel/runtime" "^7.5.5"
dom-helpers "^5.0.1"
loose-envify "^1.4.0"
prop-types "^15.6.2"
react-use-gesture@^6.0.14: react-use-gesture@^6.0.14:
version "6.0.14" version "6.0.14"
resolved "https://registry.yarnpkg.com/react-use-gesture/-/react-use-gesture-6.0.14.tgz#ab2d35ef72a5fb6060a6160eb12568c276f8a4b1" resolved "https://registry.yarnpkg.com/react-use-gesture/-/react-use-gesture-6.0.14.tgz#ab2d35ef72a5fb6060a6160eb12568c276f8a4b1"
...@@ -13056,6 +13265,11 @@ regenerator-runtime@^0.13.2, regenerator-runtime@^0.13.3: ...@@ -13056,6 +13265,11 @@ regenerator-runtime@^0.13.2, regenerator-runtime@^0.13.3:
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5"
integrity sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw== integrity sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==
regenerator-runtime@^0.13.4:
version "0.13.4"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz#e96bf612a3362d12bb69f7e8f74ffeab25c7ac91"
integrity sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g==
regenerator-transform@^0.10.0: regenerator-transform@^0.10.0:
version "0.10.1" version "0.10.1"
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd"
......
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