Commit 2ca3bf8d authored by ianlapham's avatar ianlapham

add slider to remove

parent 236c3030
......@@ -6,6 +6,7 @@
"private": true,
"dependencies": {
"@ethersproject/units": "^5.0.0-beta.132",
"@material-ui/core": "^4.9.5",
"@reach/dialog": "^0.2.8",
"@reach/tooltip": "^0.2.0",
"@types/jest": "^25.1.3",
......
import React from 'react'
import { Button as RebassButton } from 'rebass/styled-components'
import styled from 'styled-components'
import { darken } from 'polished'
import { darken, lighten } from 'polished'
import { RowBetween } from '../Row'
import { ChevronDown } from 'react-feather'
......@@ -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 }) {
return (
<ButtonPrimary {...rest}>
......
......@@ -8,9 +8,12 @@ const Card = styled(Box)`
padding: ${({ padding }) => padding};
border: ${({ border }) => border};
`
export default Card
export const LightCard = styled(Card)`
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 { ButtonPrimary } from '../Button'
......@@ -6,6 +6,7 @@ import { AutoColumn, ColumnCenter } from '../Column'
import Row, { RowBetween, RowFlat, RowFixed } from '../Row'
import { ArrowDown } from 'react-feather'
import { ButtonConfirmed } from '../Button'
import { Text } from 'rebass'
import { LightCard } from '../Card'
import Modal from '../Modal'
......@@ -35,6 +36,10 @@ const ConfirmedIcon = styled(ColumnCenter)`
padding: 60px 0;
`
const ConfirmedText = styled(Text)`
color: ${({ theme, confirmed }) => (confirmed ? theme.connectedGreen : theme.white)};
`
export default function ConfirmationModal({
isOpen,
onDismiss,
......@@ -46,23 +51,23 @@ export default function ConfirmationModal({
transactionType,
pendingConfirmation,
hash,
contractCall
signed = false,
contractCall,
attemptedRemoval = false,
extraCall = undefined
}) {
const { address: address0, symbol: symbol0 } = amount0?.token || {}
const { address: address1, symbol: symbol1 } = amount1?.token || {}
const { chainId } = useWeb3React()
const [confirmed, setConfirmed] = useState(false)
function WrappedOnDismissed() {
onDismiss()
setConfirmed(false)
}
return (
<Modal isOpen={isOpen} onDismiss={onDismiss}>
{!confirmed ? (
<Modal isOpen={isOpen} onDismiss={WrappedOnDismissed}>
{!attemptedRemoval ? (
<Wrapper>
<Section gap="40px">
<RowBetween>
......@@ -195,10 +200,38 @@ export default function ConfirmationModal({
</Text>
</RowBetween>
)}
{transactionType === TRANSACTION_TYPE.REMOVE ? (
<RowBetween gap="20px">
<ButtonConfirmed
style={{ margin: '20px 0' }}
width="48%"
onClick={() => {
extraCall()
}}
confirmed={signed}
disabled={signed}
>
<ConfirmedText fontWeight={500} fontSize={20} confirmed={signed}>
{signed ? 'Signed' : 'Sign'}
</ConfirmedText>
</ButtonConfirmed>
<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={() => {
setConfirmed(true)
contractCall()
}}
>
......@@ -211,6 +244,7 @@ export default function ConfirmationModal({
: 'Swap'}
</Text>
</ButtonPrimary>
)}
{transactionType === TRANSACTION_TYPE.ADD && (
<Text fontSize={12} color="#565A69" textAlign="center">
{`Output is estimated. You will receive at least ${liquidityAmount?.toFixed(
......
......@@ -245,7 +245,7 @@ export default function CurrencyInputPanel({
<ErrorSpan data-tip={'Enter max'} error={!!error} onClick={() => {}}></ErrorSpan>
<ClickableText onClick={onMax}>
<Text>
Balance: {customBalance ? customBalance.toSignificant(4) : userTokenBalance?.toSignificant(4)}
Balance: {customBalance ? customBalance?.toSignificant(4) : userTokenBalance?.toSignificant(4)}
</Text>
</ClickableText>
</RowBetween>
......@@ -273,7 +273,7 @@ export default function CurrencyInputPanel({
>
<Aligner>
{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 ? (
<TokenLogo address={token?.address} size={'24px'} />
) : null}
......
......@@ -7,7 +7,7 @@ export default function DoubleTokenLogo({ a0, a1, size = 16, margin = false }) {
position: relative;
display: flex;
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)`
......
import React, { useState, useReducer, useCallback, useEffect } from 'react'
import { ethers } from 'ethers'
import styled from 'styled-components'
import { ethers } from 'ethers'
import { parseUnits, parseEther } from '@ethersproject/units'
import { WETH, TradeType, Route, Trade, TokenAmount, JSBI } from '@uniswap/sdk'
import { useWeb3React } from '../../hooks'
import { useToken } from '../../contexts/Tokens'
import { useExchange } from '../../contexts/Exchanges'
import { useTransactionAdder } from '../../contexts/Transactions'
import { useWeb3React } from '../../hooks'
import { useAddressBalance } from '../../contexts/Balances'
import { useTransactionAdder } from '../../contexts/Transactions'
import { useAddressAllowance } from '../../contexts/Allowances'
import ConfirmationModal from '../ConfirmationModal'
import CurrencyInputPanel from '../CurrencyInputPanel'
import { Text } from 'rebass'
import { ButtonPrimary } from '../Button'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import { RowBetween } from '../../components/Row'
import { ButtonPrimary } from '../Button'
import { ArrowDown, ArrowUp } from 'react-feather'
import CurrencyInputPanel from '../CurrencyInputPanel'
import ConfirmationModal from '../ConfirmationModal'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import { TRANSACTION_TYPE, ROUTER_ADDRESSES } from '../../constants'
import { getRouterContract, calculateGasMargin } from '../../utils'
import { TRANSACTION_TYPE } from '../../constants'
const ArrowWrapper = styled.div`
padding: 4px;
......@@ -138,6 +138,7 @@ function reducer(
export default function ExchangePage() {
const { chainId, account, library } = useWeb3React()
const routerAddress = ROUTER_ADDRESSES[chainId]
const [state, dispatch] = useReducer(reducer, WETH[chainId].address, initializeSwapState)
const { independentField, typedValue, ...fieldData } = state
......@@ -326,8 +327,6 @@ export default function ExchangePage() {
: calculateSlippageAmount(parsedAmounts[Field.OUTPUT])?.[0]
}
const routerAddress = '0xd9210Ff5A0780E083BB40e30d005d93a2DcFA4EF'
const inputApproval = useAddressAllowance(account, tokens[Field.INPUT], 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
}
// amount of tokens to display at once
const [tokensShown, setTokensShown] = useState(0)
const [pairsShown, setPairsShown] = useState(0)
const [, setTokensShown] = useState(0)
const [, setPairsShown] = useState(0)
// filters on results
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`
font-size: ${({ size }) => size};
width: ${({ size }) => size};
height: ${({ size }) => size};
margin-bottom: -2px;
margin-right: ${({ sizeraw, margin }) => margin && (sizeraw / 2 + 10).toString() + 'px'};
margin-bottom: -4px;
`
const StyledEthereumLogo = styled(EthereumLogo)`
......@@ -35,7 +34,7 @@ const StyledEthereumLogo = styled(EthereumLogo)`
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 { chainId } = useWeb3React()
......
import React, { createContext, useContext, useReducer, useRef, useMemo, useCallback, useEffect, ReactNode } from 'react'
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 { useBlockNumber } from './Application'
import { useAllExchanges } from './Exchanges'
import { useWeb3React, useDebounce } from '../hooks'
import { getEtherBalance, getTokenBalance, isAddress } from '../utils'
const LOCAL_STORAGE_KEY = 'BALANCES'
const SHORT_BLOCK_TIMEOUT = (60 * 2) / 15 // in seconds, represented as a block number delta
......@@ -408,30 +409,23 @@ export function useAllBalances(): Array<TokenAmount> {
const allTokens = useAllTokens()
const formattedBalances = useMemo(() => {
if (!state?.[chainId]) {
if (!state || !state[chainId]) {
return {}
} else {
let newBalances = {}
Object.keys(state[chainId]).map(address => {
return Object.keys(state[chainId][address]).map(tokenAddress => {
if (state?.[chainId][address][tokenAddress].value) {
return (newBalances[chainId] = {
if (state[chainId][address][tokenAddress].value) {
newBalances[chainId] = {
...newBalances[chainId],
[address]: {
...newBalances[chainId]?.[address],
[tokenAddress]: new TokenAmount(
// if token not in token list, must be an exchange -
/**
* @TODO
*
* should we live fetch data here if token not in list
*
*/
allTokens && allTokens[tokenAddress] ? allTokens[tokenAddress] : new Token(chainId, tokenAddress, 18),
allTokens[tokenAddress] ? allTokens[tokenAddress] : new Token(chainId, tokenAddress, 18),
JSBI.BigInt(state?.[chainId][address][tokenAddress].value)
)
}
})
}
}
})
})
......@@ -485,7 +479,3 @@ export function useAccountLPBalances(account: string) {
})
}, [account, allExchanges, chainId, startListening, stopListening])
}
export function useExchangeReserves(tokenAddress: string) {
return []
}
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
import { ChainId, WETH, Token, Exchange } from '@uniswap/sdk'
import { INITIAL_TOKENS_CONTEXT } from './Tokens'
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect, useState } from 'react'
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'
......@@ -138,3 +139,46 @@ export function useAllExchanges() {
return 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 {
return token
}
export function useAllTokens() {
export function useAllTokens(): string[] {
const { chainId } = useWeb3React()
const [state] = useTokensContext()
......
......@@ -47,8 +47,8 @@ const Body = styled.div`
max-width: 28rem;
width: 90%;
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),
0px 24px 32px 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.01);
border-radius: 20px;
padding: 2rem 1rem;
`
......
......@@ -4,34 +4,51 @@ import { ethers } from 'ethers'
import { parseUnits, parseEther } from '@ethersproject/units'
import { WETH, TokenAmount, JSBI, Percent, Route } from '@uniswap/sdk'
import SearchModal from '../../components/SearchModal'
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 CurrencyInputPanel from '../../components/CurrencyInputPanel'
import { Text } from 'rebass'
import { Plus } from 'react-feather'
import { ChevronDown } from 'react-feather'
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 { ButtonPrimary, ButtonEmpty } from '../../components/Button'
import { useToken } from '../../contexts/Tokens'
import { useExchange } from '../../contexts/Exchanges'
import { useWeb3React } from '../../hooks'
import { useAddressBalance } from '../../contexts/Balances'
import { useExchangeContract } from '../../hooks'
import { useAddressAllowance } from '../../contexts/Allowances'
import { useTransactionAdder } from '../../contexts/Transactions'
import { useExchange, useTotalSupply } from '../../contexts/Exchanges'
import { BigNumber } from 'ethers/utils'
import { TRANSACTION_TYPE, ROUTER_ADDRESSES } from '../../constants'
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)`
color: ${({ theme, error }) => (error ? theme.salmonRed : theme.chaliceGray)};
`
const FixedBottom = styled.div`
position: absolute;
bottom: -240px;
width: 100%;
`
enum Field {
INPUT,
OUTPUT
......@@ -120,22 +137,23 @@ function reducer(
}
}
/**
* @todo should we ever not have prepopulated tokens?
*
*/
export default function AddLiquidity({ token0, token1 }) {
const { account, chainId, library } = useWeb3React()
const routerAddress = ROUTER_ADDRESSES[chainId]
const routerAddress: string = ROUTER_ADDRESSES[chainId]
// modal state
const [showSearch, toggleSearch] = useState(false)
// state for confirmation popup
const [showConfirm, toggleConfirm] = useState(false)
const [pendingConfirmation, toggelPendingConfirmation] = useState(true)
// modal states
const [showSearch, setShowSearch] = useState<boolean>(false)
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [pendingConfirmation, setPendingConfirmation] = useState<boolean>(true)
// input state
const [state, dispatch] = useReducer(reducer, initializeAddState(token0, token1))
const { independentField, typedValue, ...fieldData } = state
// get derived state
const dependentField = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
// get basic SDK entities
......@@ -143,17 +161,26 @@ export default function AddLiquidity({ token0, token1 }) {
[Field.INPUT]: useToken(fieldData[Field.INPUT].address),
[Field.OUTPUT]: useToken(fieldData[Field.OUTPUT].address)
}
// exhchange data
const exchange = useExchange(tokens[Field.INPUT], tokens[Field.OUTPUT])
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 = {
[Field.INPUT]: useAddressBalance(account, tokens[Field.INPUT]),
[Field.OUTPUT]: useAddressBalance(account, tokens[Field.OUTPUT])
}
// check if no exchange or no liquidity
const [noLiquidity, setNoLiquidity] = useState(false)
useEffect(() => {
if (
exchange &&
......@@ -182,8 +209,8 @@ export default function AddLiquidity({ token0, token1 }) {
}
}, [independentField, nonrelationalAmounts, tokens, typedValue, noLiquidity])
// caclulate the token amounts based on the input
const parsedAmounts: { [field: number]: TokenAmount } = {}
//if no liquidity set parsed to non relational, else get dependent calculated amounts
if (noLiquidity) {
parsedAmounts[independentField] = nonrelationalAmounts[independentField]
parsedAmounts[dependentField] = nonrelationalAmounts[dependentField]
......@@ -199,7 +226,6 @@ export default function AddLiquidity({ token0, token1 }) {
}
}
// get the price data and update dependent field
if (
route &&
!noLiquidity &&
......@@ -215,47 +241,15 @@ export default function AddLiquidity({ token0, token1 }) {
[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
const liquidityMinted =
!!exchange && !!parsedAmounts[Field.INPUT] && !!parsedAmounts[Field.OUTPUT] && !!totalPoolTokens && exchange
? exchange.getLiquidityMinted(totalPoolTokens, parsedAmounts[Field.INPUT], parsedAmounts[Field.OUTPUT])
!!exchange && !!parsedAmounts[Field.INPUT] && !!parsedAmounts[Field.OUTPUT] && !!totalSupply
? exchange.getLiquidityMinted(totalSupply, parsedAmounts[Field.INPUT], parsedAmounts[Field.OUTPUT])
: undefined
const poolTokenPercentage =
!!liquidityMinted && !!totalPoolTokens
? new Percent(liquidityMinted.raw, totalPoolTokens.add(liquidityMinted).raw)
!!liquidityMinted && !!totalSupply
? new Percent(liquidityMinted.raw, totalSupply.add(liquidityMinted).raw)
: undefined
const onTokenSelection = useCallback((field: Field, address: string) => {
......@@ -269,215 +263,218 @@ export default function AddLiquidity({ token0, token1 }) {
dispatch({ type: AddAction.TYPE, payload: { field, typedValue } })
}, [])
const onMaxInput = useCallback((typedValue: string) => {
dispatch({
type: AddAction.TYPE,
payload: {
field: Field.INPUT,
typedValue
}
})
}, [])
const onMaxOutput = useCallback((typedValue: string) => {
const onMax = useCallback((typedValue: string, field) => {
dispatch({
type: AddAction.TYPE,
payload: {
field: Field.OUTPUT,
field: field,
typedValue
}
})
}, [])
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 =
!!userBalances[Field.OUTPUT] &&
// get the max amounts user can add
const [maxAmountInput, maxAmountOutput] = [Field.INPUT, Field.OUTPUT].map(index => {
const field = Field[index]
return !!userBalances[Field[field]] &&
JSBI.greaterThan(
userBalances[Field.OUTPUT].raw,
tokens[Field.OUTPUT].equals(WETH[chainId]) ? MIN_ETHER.raw : JSBI.BigInt(0)
userBalances[Field[field]].raw,
tokens[Field[field]].equals(WETH[chainId]) ? MIN_ETHER.raw : JSBI.BigInt(0)
)
? tokens[Field.OUTPUT].equals(WETH[chainId])
? 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
})
const atMaxAmountOutput =
!!maxAmountOutput && !!parsedAmounts[Field.OUTPUT]
? JSBI.equal(maxAmountOutput.raw, parsedAmounts[Field.OUTPUT].raw)
const [atMaxAmountInput, atMaxAmountOutput] = [Field.INPUT, Field.OUTPUT].map(index => {
const field = Field[index]
const maxAmount = index === Field.INPUT ? maxAmountInput : maxAmountOutput
return !!maxAmount && !!parsedAmounts[Field[field]]
? JSBI.lessThanOrEqual(maxAmount.raw, parsedAmounts[Field[field]].raw)
: 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
useEffect(() => {
if (
parsedAmounts[Field.INPUT] &&
inputApproval &&
JSBI.greaterThan(parsedAmounts[Field.INPUT].raw, inputApproval.raw)
) {
setShowInputUnlock(true)
} else {
setShowInputUnlock(false)
}
if (
setShowInputUnlock(
parsedAmounts[Field.INPUT] && inputApproval && JSBI.greaterThan(parsedAmounts[Field.INPUT].raw, inputApproval.raw)
)
setShowOutputUnlock(
parsedAmounts[Field.OUTPUT] &&
outputApproval &&
JSBI.greaterThan(parsedAmounts[Field.OUTPUT]?.raw, outputApproval?.raw)
) {
setShowOutputUnlock(true)
} else {
setShowOutputUnlock(false)
}
JSBI.greaterThan(parsedAmounts[Field.OUTPUT].raw, outputApproval.raw)
)
}, [inputApproval, outputApproval, parsedAmounts])
// errors
const [generalError, setGeneralError] = useState()
const [inputError, setInputError] = useState()
const [outputError, setOutputError] = useState()
const [errorText, setErrorText] = useState(' ')
const [isError, setIsError] = useState(false)
const [isValid, setIsValid] = useState(false)
// update errors live
useEffect(() => {
// reset errors
setGeneralError(null)
setInputError(null)
setOutputError(null)
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) {
setInputError('Need to approve amount on input.')
setIsValid(false)
}
if (showOutputUnlock) {
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.')
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.')
setIsError(true)
setIsValid(false)
}
}, [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
const addTransaction = useTransactionAdder()
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() {
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]
const minTokenOutput = calculateSlippageAmount(parsedAmounts[Field.OUTPUT])[0]
const deadline = Math.ceil(Date.now() / 1000) + DEADLINE_FROM_NOW
let method, estimate, args, value
if (tokens[Field.INPUT] === WETH[chainId] || tokens[Field.OUTPUT] === WETH[chainId]) {
method = router.addLiquidityETH
estimate = router.estimate.addLiquidityETH
args = [
tokens[Field.OUTPUT] === WETH[chainId] ? tokens[Field.INPUT].address : tokens[Field.OUTPUT].address, // token
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.toString(),
1739591241
account,
deadline
]
value = ethers.constants.Zero
}
const estimatedGasLimit = await router.estimate.addLiquidity(...args, {
value: ethers.constants.Zero
const estimatedGasLimit = await estimate(...args, {
value: value
})
const GAS_MARGIN = ethers.utils.bigNumberify(1000)
router
.addLiquidity(...args, {
gasLimit: calculateGasMargin(estimatedGasLimit, GAS_MARGIN)
method(...args, {
gasLimit: calculateGasMargin(estimatedGasLimit, GAS_MARGIN),
value: value
})
.then(response => {
setTxHash(response)
setTxHash(response.hash)
addTransaction(response)
toggelPendingConfirmation(false)
setPendingConfirmation(false)
})
.catch(e => {
toggleConfirm(false)
.catch((e: Error) => {
console.log(e)
setShowConfirm(false)
})
}
return (
<>
<Wrapper>
<ConfirmationModal
isOpen={showConfirm}
onDismiss={() => {
toggleConfirm(false)
setShowConfirm(false)
}}
liquidityAmount={liquidityMinted}
amount0={
parsedAmounts[independentField]?.token.equals(exchange?.token0)
? parsedAmounts[independentField]
: parsedAmounts[dependentField]
}
amount1={
parsedAmounts[independentField]?.token.equals(exchange?.token0)
? parsedAmounts[dependentField]
: parsedAmounts[independentField]
}
amount0={parsedAmounts[Field.INPUT]}
amount1={parsedAmounts[Field.OUTPUT]}
poolTokenPercentage={poolTokenPercentage}
price={route?.midPrice && route?.midPrice?.raw?.denominator}
transactionType={TRANSACTION_TYPE.ADD}
contractCall={onAdd}
pendingConfirmation={pendingConfirmation}
hash={txHash ? txHash.hash : ''}
hash={txHash ? txHash : ''}
/>
<SearchModal
isOpen={showSearch}
onDismiss={() => {
toggleSearch(false)
setShowSearch(false)
}}
/>
<AutoColumn gap="20px">
<ButtonEmpty
padding={'1rem'}
onClick={() => {
toggleSearch(true)
setShowSearch(true)
}}
>
<RowBetween>
<DoubleLogo a0={exchange?.token0?.address || ''} a1={exchange?.token1?.address || ''} size={24} />
<Text fontSize={20}>
{exchange?.token0?.symbol && exchange?.token1?.symbol
? exchange?.token0?.symbol + ' / ' + exchange?.token1?.symbol + ' Pool'
{exchange?.token0 && exchange?.token1
? exchange.token0.symbol + ' / ' + exchange.token1.symbol + ' Pool'
: ''}
</Text>
<ChevronDown size={24} />
......@@ -498,7 +495,7 @@ export default function AddLiquidity({ token0, token1 }) {
value={formattedAmounts[Field.INPUT]}
onUserInput={onUserInput}
onMax={() => {
maxAmountInput && onMaxInput(maxAmountInput.toExact())
maxAmountInput && onMax(maxAmountInput.toExact(), Field.INPUT)
}}
atMax={atMaxAmountInput}
token={tokens[Field.INPUT]}
......@@ -517,7 +514,7 @@ export default function AddLiquidity({ token0, token1 }) {
value={formattedAmounts[Field.OUTPUT]}
onUserInput={onUserInput}
onMax={() => {
onMaxOutput(maxAmountOutput.toExact())
onMax(maxAmountOutput.toExact(), Field.OUTPUT)
}}
atMax={atMaxAmountOutput}
token={tokens[Field.OUTPUT]}
......@@ -528,39 +525,21 @@ export default function AddLiquidity({ token0, token1 }) {
showUnlock={showOutputUnlock}
disableTokenSelect
/>
<ColumnCenter>
<ArrowDown size="16" color="#888D9B" />
</ColumnCenter>
<LightCard>
<AutoColumn gap="10px">
<RowBetween>
Minted pool tokens:
<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}
1 {tokens[independentField].symbol} = {route?.midPrice?.toSignificant(6)}
{tokens[dependentField].symbol}
</div>
</RowBetween>
</AutoColumn>
</LightCard>
<ColumnCenter style={{ height: '20px' }}>
<ErrorText fontSize={12} error={isError}>
{errorText && errorText}
{generalError ? generalError : inputError ? inputError : outputError ? outputError : ''}
</ErrorText>
</ColumnCenter>
<ButtonPrimary
onClick={() => {
toggleConfirm(true)
setShowConfirm(true)
}}
disabled={!isValid}
>
......@@ -568,7 +547,15 @@ export default function AddLiquidity({ token0, token1 }) {
Supply
</Text>
</ButtonPrimary>
<FixedBottom>
<PositionCard
exchangeAddress={exchange?.liquidityToken?.address}
token0={tokens[Field.INPUT]}
token1={tokens[Field.OUTPUT]}
minimal={true}
/>
</FixedBottom>
</AutoColumn>
</>
</Wrapper>
)
}
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 { ethers } from 'ethers'
import { parseUnits } from '@ethersproject/units'
import { TokenAmount, JSBI, Route, WETH } from '@uniswap/sdk'
import { Text } from 'rebass'
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 Slider from '../../components/Slider'
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 { Text } from 'rebass'
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 { useAddressBalance, useAllBalances } from '../../contexts/Balances'
import { useExchange } from '../../contexts/Exchanges'
import { useExchangeContract } from '../../hooks'
import { useWeb3React } from '../../hooks'
import { useAllBalances } from '../../contexts/Balances'
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 { ROUTER_ADDRESSES } from '../../constants'
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 ClickableText = styled(Text)`
:hover {
cursor: pointer;
}
`
const ErrorText = styled(Text)`
color: ${({ theme, error }) => (error ? theme.salmonRed : theme.chaliceGray)};
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 {
PERCENTAGE,
POOL,
INPUT,
OUTPUT
......@@ -53,7 +95,7 @@ interface RemoveState {
function initializeRemoveState(inputAddress?: string, outputAddress?: string): RemoveState {
return {
independentField: Field.INPUT,
independentField: Field.PERCENTAGE,
typedValue: '',
[Field.POOL]: {
address: ''
......@@ -110,12 +152,12 @@ function reducer(
export default function RemoveLiquidity({ token0, token1 }) {
// console.log('DEBUG: Rendering')
const { account, chainId, library, connector } = useWeb3React()
const { account, chainId, library } = useWeb3React()
const routerAddress = ROUTER_ADDRESSES[chainId]
// modal state
const [showSearch, toggleSearch] = useState(false)
const [pendingConfirmation, toggelPendingConfirmation] = useState(false)
const [pendingConfirmation, setPendingConfirmation] = useState(true)
// input state
const [state, dispatch] = useReducer(reducer, initializeRemoveState(token0, token1))
......@@ -134,53 +176,13 @@ export default function RemoveLiquidity({ token0, token1 }) {
const exchangeContract = useExchangeContract(exchange?.liquidityToken.address)
// pool token data
const [totalPoolTokens, setTotalPoolTokens] = useState<TokenAmount>()
// get user- and token-specific lookup data
const userBalances = {
[Field.INPUT]: useAddressBalance(account, tokens[Field.INPUT]),
[Field.OUTPUT]: useAddressBalance(account, tokens[Field.OUTPUT])
}
const totalPoolTokens = useTotalSupply(exchange)
const allBalances = useAllBalances()
const userLiquidity = allBalances?.[account]?.[exchange.liquidityToken.address]
const userLiquidity = allBalances?.[account]?.[exchange?.liquidityToken?.address]
// state for confirmation popup
const [showConfirm, toggleConfirm] = 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 [showConfirm, setShowConfirm] = useState(false)
const TokensDeposited = {
[Field.INPUT]:
......@@ -196,23 +198,76 @@ export default function RemoveLiquidity({ token0, token1 }) {
}
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
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
const parsedAmounts: { [field: number]: TokenAmount } = {}
// 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) {
try {
const typedValueParsed = parseUnits(typedValue, tokens[Field.INPUT].decimals).toString()
if (typedValueParsed !== '0') {
parsedAmounts[Field.INPUT] = new TokenAmount(tokens[Field.INPUT], typedValueParsed)
parsedAmounts[Field.OUTPUT] = route.midPrice.quote(parsedAmounts[Field.INPUT])
parsedAmounts[Field.POOL] = exchange.getLiquidityMinted(
// first get the exact percentage of tokens deposited
const inputTokenAmount = new TokenAmount(tokens[Field.INPUT], typedValueParsed)
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,
parsedAmounts[Field.INPUT],
parsedAmounts[Field.OUTPUT]
parsedAmounts[Field.POOL],
false
)
}
} catch (error) {
......@@ -225,12 +280,16 @@ export default function RemoveLiquidity({ token0, token1 }) {
try {
const typedValueParsed = parseUnits(typedValue, tokens[Field.OUTPUT].decimals).toString()
if (typedValueParsed !== '0') {
parsedAmounts[Field.OUTPUT] = new TokenAmount(tokens[Field.OUTPUT], typedValueParsed)
parsedAmounts[Field.INPUT] = route.midPrice.quote(parsedAmounts[Field.OUTPUT])
parsedAmounts[Field.POOL] = exchange.getLiquidityMinted(
const inputTokenAmount = new TokenAmount(tokens[Field.OUTPUT], typedValueParsed)
const ratio = JSBI.divide(inputTokenAmount.raw, TokensDeposited[Field.OUTPUT].raw)
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,
parsedAmounts[Field.INPUT],
parsedAmounts[Field.OUTPUT]
parsedAmounts[Field.POOL],
false
)
}
} catch (error) {
......@@ -238,16 +297,21 @@ export default function RemoveLiquidity({ token0, token1 }) {
console.error(error)
}
}
} else {
} else if (independentField === Field.POOL) {
if (typedValue !== '' && typedValue !== '.' && exchange) {
try {
const typedValueParsed = parseUnits(typedValue, exchange?.liquidityToken.decimals).toString()
if (typedValueParsed !== '0') {
parsedAmounts[Field.POOL] = new TokenAmount(exchange?.liquidityToken, typedValueParsed)
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)
) {
/**
* do some error catching on amount?
*/
} else {
parsedAmounts[Field.INPUT] = exchange.getLiquidityValue(
tokens[Field.INPUT],
......@@ -292,97 +356,64 @@ export default function RemoveLiquidity({ token0, token1 }) {
: ''
}
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 } })
}, [])
const onMaxInput = useCallback((typedValue: string) => {
dispatch({
type: RemoveAction.TYPE,
payload: {
field: Field.INPUT,
typedValue
}
})
}, [])
const onMaxOutput = useCallback((typedValue: string) => {
const onMax = useCallback((typedValue: string, field) => {
dispatch({
type: RemoveAction.TYPE,
payload: {
field: Field.OUTPUT,
field: field,
typedValue
}
})
}, [])
const onMaxPool = useCallback((typedValue: string) => {
dispatch({
type: RemoveAction.TYPE,
payload: {
field: Field.POOL,
typedValue
}
})
}, [])
const MIN_ETHER = new TokenAmount(WETH[chainId], JSBI.BigInt(parseEther('.01')))
const maxAmountInput =
TokensDeposited[Field.INPUT] &&
JSBI.greaterThan(
TokensDeposited[Field.INPUT].raw,
tokens[Field.INPUT].equals(WETH[chainId]) ? MIN_ETHER.raw : JSBI.BigInt(0)
)
? 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]
// get the max amounts user can add
const maxAmountPoolToken = userLiquidity
const [maxAmountInput, maxAmountOutput] = [Field.INPUT, Field.OUTPUT].map(index => {
const field = Field[index]
return !!TokensDeposited[Field[field]] && JSBI.greaterThan(TokensDeposited[Field[field]].raw, JSBI.BigInt(0))
? TokensDeposited[Field[field]]
: undefined
})
const atMaxAmountOutput =
!!maxAmountOutput && !!parsedAmounts[Field.OUTPUT]
? JSBI.equal(maxAmountOutput.raw, parsedAmounts[Field.OUTPUT].raw)
const [atMaxAmountInput, atMaxAmountOutput] = [Field.INPUT, Field.OUTPUT].map(index => {
const field = Field[index]
const maxAmount = index === Field.INPUT ? maxAmountInput : maxAmountOutput
return !!maxAmount && !!parsedAmounts[Field[field]]
? JSBI.lessThanOrEqual(maxAmount.raw, parsedAmounts[Field[field]].raw)
: undefined
const maxAmountPoolToken = userLiquidity
})
const atMaxAmountPoolToken =
!!maxAmountOutput && !!parsedAmounts[Field.POOL]
? JSBI.equal(maxAmountPoolToken.raw, parsedAmounts[Field.POOL].raw)
? JSBI.lessThanOrEqual(maxAmountPoolToken.raw, parsedAmounts[Field.POOL].raw)
: 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
useEffect(() => {
// reset errors
setGeneralError(false)
setInputError(null)
setOutputError(null)
setPoolTokenError(null)
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 (
totalPoolTokens &&
userLiquidity &&
......@@ -392,50 +423,44 @@ export default function RemoveLiquidity({ token0, token1 }) {
) {
setPoolTokenError('Input a liquidity amount less than or equal to your balance.')
setIsError(true)
}
if (parseFloat(parsedAmounts?.[Field.INPUT]?.toExact()) > parseFloat(userBalances?.[Field.INPUT]?.toExact())) {
setInputError('Insufficient balance.')
setIsError(true)
}
if (parseFloat(parsedAmounts?.[Field.OUTPUT]?.toExact()) > parseFloat(userBalances?.[Field.OUTPUT]?.toExact())) {
setOutputError('Insufficient balance.')
setIsError(true)
}
}, [parsedAmounts, totalPoolTokens, userBalances, userLiquidity])
// set error text based on all errors
useEffect(() => {
setErrorText(null)
if (poolTokenError) {
setErrorText(poolTokenError)
} else if (!parsedAmounts[Field.INPUT]) {
setErrorText('Enter an amount to continue')
} else if (outputError) {
setErrorText(outputError)
} else if (inputError) {
setErrorText(inputError)
return
}
}, [inputError, outputError, parsedAmounts, poolTokenError])
setIsValid(false)
}
// if (
// parsedAmounts?.[Field.INPUT] &&
// userBalances?.[Field.INPUT] &&
// JSBI.greaterThan(parsedAmounts?.[Field.INPUT]?.raw, userBalances?.[Field.INPUT]?.raw)
// ) {
// setInputError('Insufficient balance.')
// setIsError(true)
// setIsValid(false)
// }
// if (
// parsedAmounts?.[Field.OUTPUT] &&
// userBalances?.[Field.OUTPUT] &&
// parseFloat(parsedAmounts?.[Field.OUTPUT]?.toExact()) > parseFloat(userBalances?.[Field.OUTPUT]?.toExact())
// ) {
// setOutputError('Insufficient balance.')
// setIsError(true)
// setIsValid(false)
// }
}, [parsedAmounts, totalPoolTokens, userLiquidity])
// error state for button
const isValid = !errorText
// state for txn
const addTransaction = useTransactionAdder()
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 [signed, setSigned] = useState(false)
const [attemptedRemoval, setAttemptedRemoval] = useState(false)
const [deadline, setDeadline] = useState()
async function onSign() {
const nonce = await exchangeContract.nonces(account)
const deadline = 1739591241
const newDeadline = Math.ceil(Date.now() / 1000) + DEADLINE_FROM_NOW
setDeadline(newDeadline)
const EIP712Domain = [
{ name: 'name', type: 'string' },
......@@ -463,7 +488,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
spender: routerAddress,
value: parsedAmounts[Field.POOL].raw.toString(),
nonce: nonce.toHexString(),
deadline: deadline
deadline: newDeadline
}
const data = JSON.stringify({
types: {
......@@ -478,58 +503,103 @@ export default function RemoveLiquidity({ token0, token1 }) {
library.send('eth_signTypedData_v4', [account, data]).then(_signature => {
const signature = splitSignature(_signature)
setSigInputs([signature.v, signature.r, signature.s])
setSigned(true)
})
}
async function onRemove() {
// now can structure txn
const args = [
setAttemptedRemoval(true)
const router = getRouterContract(chainId, library, account)
let method, args, estimate
// removal with ETH
if (tokens[Field.INPUT] === WETH[chainId] || tokens[Field.OUTPUT] === WETH[chainId]) {
method = router.removeLiquidityETHWithPermit
estimate = router.estimate.removeLiquidityETHWithPermit
args = [
tokens[Field.OUTPUT] === WETH[chainId] ? tokens[Field.INPUT].address : tokens[Field.OUTPUT].address,
parsedAmounts[Field.POOL].raw.toString(),
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,
1739591241,
deadline,
sigInputs[0],
sigInputs[1],
sigInputs[2]
]
}
const estimatedGasLimit = await router.estimate.removeLiquidityWithPermit(...args, {
const estimatedGasLimit = await estimate(...args, {
value: ethers.constants.Zero
}).catch(() => {
resetModalState()
setShowConfirm(false)
})
const GAS_MARGIN = ethers.utils.bigNumberify(1000)
router
.removeLiquidityWithPermit(...args, {
method(...args, {
gasLimit: calculateGasMargin(estimatedGasLimit, GAS_MARGIN)
})
.then(response => {
console.log('success')
setPendingConfirmation(false)
setTxHash(response.hash)
addTransaction(response.hash)
addTransaction(response)
})
.catch(e => {
console.log('error trying txn')
.catch(() => {
resetModalState()
setShowConfirm(false)
})
}
function resetModalState() {
setSigned(false)
setSigInputs(null)
setAttemptedRemoval(false)
setPendingConfirmation(true)
}
// show advanced mode or not
const [showAdvanced, setShowAdvanced] = useState(false)
return (
<>
<Wrapper>
<ConfirmationModal
isOpen={showConfirm}
onDismiss={() => {
toggleConfirm(false)
resetModalState()
setShowConfirm(false)
}}
amount0={parsedAmounts[Field.INPUT]}
amount1={parsedAmounts[Field.OUTPUT]}
price={route?.midPrice}
liquidityAmount={parsedAmounts[Field.POOL]}
transactionType={TRANSACTION_TYPE.REMOVE}
contractCall={() => {}}
contractCall={onRemove}
extraCall={onSign}
signed={signed}
attemptedRemoval={attemptedRemoval}
pendingConfirmation={pendingConfirmation}
hash={''}
hash={txHash ? txHash : ''}
/>
<SearchModal
isOpen={showSearch}
......@@ -554,12 +624,79 @@ export default function RemoveLiquidity({ token0, token1 }) {
<ChevronDown size={24} />
</RowBetween>
</ButtonEmpty>
<LightCard>
<AutoColumn gap="20px">
<RowBetween>
<Text fontWeight={500}>Amount To Remove</Text>
<ClickableText
fontWeight={500}
onClick={() => {
setShowAdvanced(!showAdvanced)
}}
color="#2172E5"
>
{showAdvanced ? 'Minimal' : 'Advanced'}
</ClickableText>
</RowBetween>
<RowBetween style={{ alignItems: 'flex-end' }}>
<Text fontSize={72} fontWeight={500}>
{percentageAmount}%
</Text>
<MaxButton
onClick={() => {
setPercentageAmount(100)
}}
>
Max
</MaxButton>
</RowBetween>
<Slider value={percentageAmount} onChange={handleSliderChange} />
</AutoColumn>
</LightCard>
{!showAdvanced && (
<>
<ColumnCenter>
<ArrowDown size="16" color="#888D9B" />
</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 && onMaxPool(maxAmountPoolToken.toExact())
maxAmountPoolToken && onMax(maxAmountPoolToken.toExact(), Field.POOL)
}}
atMax={atMaxAmountPoolToken}
onTokenSelection={onTokenSelection}
......@@ -578,7 +715,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
value={formattedAmounts[Field.INPUT]}
onUserInput={onUserInput}
onMax={() => {
maxAmountInput && onMaxInput(maxAmountInput.toExact())
maxAmountInput && onMax(maxAmountInput.toExact(), Field.INPUT)
}}
atMax={atMaxAmountInput}
token={tokens[Field.INPUT]}
......@@ -596,7 +733,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
value={formattedAmounts[Field.OUTPUT]}
onUserInput={onUserInput}
onMax={() => {
maxAmountOutput && onMaxOutput(maxAmountOutput.toExact())
maxAmountOutput && onMax(maxAmountOutput.toExact(), Field.OUTPUT)
}}
atMax={atMaxAmountOutput}
token={tokens[Field.OUTPUT]}
......@@ -606,23 +743,8 @@ export default function RemoveLiquidity({ token0, token1 }) {
disableTokenSelect
customBalance={TokensDeposited[Field.OUTPUT]}
/>
<ColumnCenter>
<ArrowDown size="16" color="#888D9B" />
</ColumnCenter>
<LightCard>
<AutoColumn gap="10px">
<RowBetween>
Pool Tokens Burned:
<div>{formattedAmounts[Field.POOL] ? formattedAmounts[Field.POOL] : '-'}</div>
</RowBetween>
<RowBetween>
{exchange?.token0.symbol} Removed:
<div>{formattedAmounts[Field.INPUT] ? formattedAmounts[Field.INPUT] : '-'}</div>
</RowBetween>
<RowBetween>
{exchange?.token1.symbol} Removed:
<div>{formattedAmounts[Field.OUTPUT] ? formattedAmounts[Field.OUTPUT] : '-'}</div>
</RowBetween>
</>
)}
<RowBetween>
Rate:
<div>
......@@ -633,40 +755,25 @@ export default function RemoveLiquidity({ token0, token1 }) {
{exchange?.token1.symbol}
</div>
</RowBetween>
</AutoColumn>
</LightCard>
<ColumnCenter style={{ height: '20px' }}>
<ErrorText fontSize={12} error={isError}>
{errorText && errorText}
</ErrorText>
</ColumnCenter>
<RowBetween>
<ButtonPrimary
onClick={() => {
// toggleConfirm(true)
onSign()
}}
width="48%"
disabled={!isValid}
>
<Text fontSize={20} fontWeight={500}>
Sign
</Text>
</ButtonPrimary>
<ButtonPrimary
onClick={() => {
// toggleConfirm(true)
onRemove()
setShowConfirm(true)
}}
width="48%"
disabled={!isValid}
>
<Text fontSize={20} fontWeight={500}>
Remove
</Text>
</ButtonPrimary>
</RowBetween>
<FixedBottom>
<PositionCard
exchangeAddress={exchange.liquidityToken.address}
token0={exchange.token0}
token1={exchange.token1}
minimal={true}
/>
</FixedBottom>
</AutoColumn>
</>
</Wrapper>
)
}
import React, { useEffect, useState, useCallback } from 'react'
import React, { useState } from 'react'
import styled from 'styled-components'
import { TokenAmount, JSBI, Percent, Token } from '@uniswap/sdk'
import ReactGA from 'react-ga'
import { withRouter } from 'react-router-dom'
import { JSBI } from '@uniswap/sdk'
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 { Text } from 'rebass'
import DoubleLogo from '../../components/DoubleLogo'
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 { 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'
import { ButtonDropwdown } from '../../components/Button'
const Positions = styled.div`
position: relative;
margin-top: 38px;
`
const FixedBottom = styled.div`
position: absolute;
bottom: -240px;
width: 100%;
`
function ExchangeCard({ exchangeAddress, token0, token1, history, allBalances }) {
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)
}, [])
export default function Supply() {
const { account } = useWeb3React()
const [showSearch, toggleSearch] = useState(false)
const exchanges = useAllExchanges()
const [showSearch, setShowSearch] = useState(false)
const allTokens = useAllTokens()
const allBalances = useAllBalances()
const allExchanges = useAllExchanges()
// initiate listener for LP balances
useAccountLPBalances(account)
const filteredPairList = Object.keys(exchanges).map((exchangeAddress, i) => {
const exchange = exchanges[exchangeAddress]
// gate on positive address
if (allBalances?.[account]?.[exchangeAddress]) {
const token0 = allTokens[exchange.token0]
const token1 = allTokens[exchange.token1]
const filteredExchangeList = Object.keys(allExchanges).map((exchangeAddress, i) => {
const balance = allBalances?.[account]?.[exchangeAddress]
return (
<ExchangeCard
history={history}
balance &&
JSBI.greaterThan(balance.raw, JSBI.BigInt(0)) && (
<PositionCard
key={i}
exchangeAddress={exchangeAddress}
token0={token0}
token1={token1}
allBalances={allBalances}
token0={allTokens[allExchanges[exchangeAddress].token0]}
token1={allTokens[allExchanges[exchangeAddress].token1]}
/>
)
} else {
return ''
}
)
})
return (
<>
<ButtonDropwdown
onClick={() => {
toggleSearch(true)
setShowSearch(true)
}}
>
<Text fontSize={20}>Find a pool</Text>
......@@ -193,7 +70,7 @@ function Supply({ history }) {
<Text fontWeight={500}>View on Uniswap.info</Text>
</Link>
</RowBetween>
{filteredPairList}
{filteredExchangeList}
</AutoColumn>
<FixedBottom>
<Card bg="rgba(255, 255, 255, 0.6)" padding={'24px'}>
......@@ -216,11 +93,9 @@ function Supply({ history }) {
<SearchModal
isOpen={showSearch}
onDismiss={() => {
toggleSearch(false)
setShowSearch(false)
}}
/>
</>
)
}
export default withRouter(Supply)
......@@ -6,15 +6,7 @@ import ROUTER_ABI from '../constants/abis/router'
import ERC20_ABI from '../constants/abis/erc20'
import ERC20_BYTES32_ABI from '../constants/abis/erc20_bytes32'
import { FACTORY_ADDRESSES, SUPPORTED_THEMES } from '../constants'
import {
BigNumber,
bigNumberify,
getAddress,
keccak256,
defaultAbiCoder,
toUtf8Bytes,
solidityPack
} from 'ethers/utils'
import { bigNumberify, keccak256, defaultAbiCoder, toUtf8Bytes, solidityPack } from 'ethers/utils'
import UncheckedJsonRpcSigner from './signer'
......
......@@ -1039,6 +1039,13 @@
dependencies:
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":
version "7.7.4"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.7.4.tgz#428a7d9eecffe27deac0a98e23bf8e3675d2a77b"
......@@ -1121,7 +1128,7 @@
"@emotion/utils" "0.11.3"
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"
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.7.4.tgz#f14932887422c9056b15a8d222a9074a7dfa2831"
integrity sha512-fxfMSBMX3tlIbKUdtGKxqB1fyrH6gVrX39Gsv3y8lRYKUqlgDt3UMqQyGnR1bQMa2B8aGnhLZokZgg8vT0Le+A==
......@@ -1697,6 +1704,69 @@
resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-4.72.0.tgz#43df23af013ad1135407e5cf33ca6e4c4c7708d5"
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":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
......@@ -2198,6 +2268,13 @@
dependencies:
"@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@*":
version "16.9.17"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.17.tgz#58f0cc0e9ec2425d1441dd7b623421a867aa253e"
......@@ -4670,6 +4747,11 @@ cloneable-readable@^1.0.0:
process-nextick-args "^2.0.0"
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:
version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
......@@ -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"
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:
version "2.1.3"
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:
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.8.tgz#0fb6fc2417ffd2816a418c9336da74d7f07db431"
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:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
......@@ -5723,6 +5818,14 @@ dom-converter@^0.2:
dependencies:
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:
version "0.2.2"
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:
dependencies:
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:
version "2.0.0"
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:
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
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:
version "3.1.1"
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:
resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554"
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:
version "4.0.1"
resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8"
......@@ -9596,6 +9716,75 @@ jsprim@^1.2.2:
json-schema "0.2.3"
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:
version "2.2.3"
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:
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.3.tgz#1438f98d046acf7b4d78cd502bf418ac64d4f095"
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:
version "1.0.25"
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
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c"
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:
version "1.2.0"
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:
dependencies:
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:
version "6.0.14"
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:
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5"
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:
version "0.10.1"
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