UI Updates, Route Context Updates (#679)

* UI updates, big fixes on route hook

* fix bug on all pair list

* logout fix and  add mainnet WETH
This commit is contained in:
Ian Lapham 2020-04-15 20:56:59 -04:00 committed by GitHub
parent af6add09a0
commit 50c9d9973a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 662 additions and 386 deletions

@ -9,7 +9,7 @@
"swapAnyway": "Swap Anyway",
"send": "Send",
"sendAnyway": "Send Anyway",
"pool": "Supply",
"pool": "Pool",
"betaWarning": "This project is in beta. Use at your own risk.",
"input": "Input",
"output": "Output",
@ -85,5 +85,7 @@
"enterTokenCont": "Enter a token address to continue",
"priceChange": "Expected price slippage",
"forAtLeast": "for at least ",
"brokenToken": "The selected token is not compatible with Uniswap V1. Adding liquidity will result in locked funds."
"brokenToken": "The selected token is not compatible with Uniswap V1. Adding liquidity will result in locked funds.",
"toleranceExplanation": "Lowering this limit decreases your risk of frontrunning. However, this makes more likely that your transaction will fail due to normal price movements.",
"tokenSearchPlaceholder": "Search name or paste token address"
}

@ -2,8 +2,7 @@ import React, { useState, useEffect } from 'react'
import styled from 'styled-components'
import { transparentize } from 'polished'
import QR from '../../assets/svg/QR.svg'
// import QR from '../../assets/svg/QR.svg'
import { isAddress } from '../../utils'
import { useWeb3React, useDebounce } from '../../hooks'
@ -15,6 +14,8 @@ const InputPanel = styled.div`
background-color: ${({ theme }) => theme.bg1};
z-index: 1;
width: 100%;
height: 60px;
`
const ContainerRow = styled.div`
@ -22,6 +23,7 @@ const ContainerRow = styled.div`
justify-content: center;
align-items: center;
border-radius: 1.25rem;
height: 60px;
border: 1px solid ${({ error, theme }) => (error ? theme.red1 : theme.bg3)};
background-color: ${({ theme }) => theme.bg1};
`
@ -43,25 +45,26 @@ const Input = styled.input`
flex: 1 1 auto;
width: 0;
background-color: ${({ theme }) => theme.bg1};
font-size: 20px;
color: ${({ error, theme }) => (error ? theme.red1 : theme.blue1)};
overflow: hidden;
text-overflow: ellipsis;
font-weight: 500;
::placeholder {
color: ${({ theme }) => theme.text4};
}
`
const QRWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
border: 1px solid ${({ theme }) => theme.bg3};
background: #fbfbfb;
padding: 4px;
border-radius: 8px;
`
// const QRWrapper = styled.div`
// display: flex;
// align-items: center;
// justify-content: center;
// border: 1px solid ${({ theme }) => theme.bg3};
// background: #fbfbfb;
// padding: 4px;
// border-radius: 8px;
// `
export default function AddressInputPanel({ initialInput = '', onChange, onError }) {
const { library } = useWeb3React()
@ -172,14 +175,14 @@ export default function AddressInputPanel({ initialInput = '', onChange, onError
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
placeholder="0x1234..."
placeholder="Recipient Address"
error={input !== '' && error}
onChange={onInput}
value={input}
/>
<QRWrapper>
{/* <QRWrapper>
<img src={QR} alt="" />
</QRWrapper>
</QRWrapper> */}
</InputRow>
</InputContainer>
</ContainerRow>

@ -7,6 +7,7 @@ import { Link } from '../../theme/components'
import { TYPE } from '../../theme'
import { AutoColumn } from '../../components/Column'
import { ButtonRadio } from '../Button'
import { useTranslation } from 'react-i18next'
import Row, { RowBetween, RowFixed } from '../../components/Row'
const InputWrapper = styled(RowBetween)`
@ -27,6 +28,9 @@ const SLIPPAGE_INDEX = {
}
export default function AdvancedSettings({ setIsOpen, setDeadline, allowedSlippage, setAllowedSlippage }) {
// text translation
const { t } = useTranslation()
const [deadlineInput, setDeadlineInput] = useState(15)
const [slippageInput, setSlippageInput] = useState()
const [activeIndex, setActiveIndex] = useState(SLIPPAGE_INDEX[3])
@ -82,8 +86,8 @@ export default function AdvancedSettings({ setIsOpen, setDeadline, allowedSlippa
back
</Link>
<RowBetween>
<TYPE.main>Limit additional price impact</TYPE.main>
<QuestionHelper text="" />
<TYPE.main>Limit front-running tolerance</TYPE.main>
<QuestionHelper text={t('toleranceExplanation')} />
</RowBetween>
<Row>
<ButtonRadio

@ -44,6 +44,24 @@ export const ButtonPrimary = styled(Base)`
}
`
export const ButtonLight = styled(Base)`
background-color: ${({ theme }) => theme.blue5};
color: ${({ theme }) => theme.blue1};
font-size: 20px;
font-weight: 500;
&:focus {
box-shadow: 0 0 0 1pt ${({ theme, disabled }) => !disabled && darken(0.05, theme.blue5)};
background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.blue5)};
}
&:hover {
background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.blue5)};
}
&:active {
box-shadow: 0 0 0 1pt ${({ theme, disabled }) => !disabled && darken(0.1, theme.blue5)};
background-color: ${({ theme, disabled }) => !disabled && darken(0.1, theme.blue5)};
}
`
export const ButtonSecondary = styled(Base)`
background-color: #ebf4ff;
color: #2172e5;
@ -73,8 +91,6 @@ export const ButtonPink = styled(Base)`
background-color: ${({ theme }) => theme.pink2};
color: white;
padding: 10px;
&:focus {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.pink2)};
background-color: ${({ theme }) => darken(0.05, theme.pink2)};

@ -18,12 +18,24 @@ export const LightCard = styled(Card)`
`
export const GreyCard = styled(Card)`
background-color: rgba(255, 255, 255, 0.9);
background-color: rgba(255, 255, 255, 0.6);
`
export const YellowCard = styled(Card)`
background-color: rgba(243, 190, 30, 0.3);
color: ${({ theme }) => theme.yellow2};
fontweight: 500;
`
export const PinkCard = styled(Card)`
background-color: rgba(255, 0, 122, 0.03);
color: ${({ theme }) => theme.pink2};
fontweight: 500;
`
const BlueCardStyled = styled(Card)`
background-color: #ebf4ff;
color: #2172e5;
background-color: ${({ theme }) => theme.blue5};
color: ${({ theme }) => theme.blue1};
border-radius: 12px;
padding: 8px;
width: fit-content;

@ -23,6 +23,8 @@ const Section = styled(AutoColumn)`
const BottomSection = styled(Section)`
background-color: ${({ theme }) => theme.bg2};
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
`
const ConfirmedIcon = styled(ColumnCenter)`

@ -1,9 +1,7 @@
import React, { useState } from 'react'
import styled from 'styled-components'
import '@reach/tooltip/styles.css'
import { ethers } from 'ethers'
import { darken } from 'polished'
import { WETH } from '@uniswap/sdk'
import TokenLogo from '../TokenLogo'
import DoubleLogo from '../DoubleLogo'
@ -16,30 +14,7 @@ import { Input as NumericalInput } from '../NumericalInput'
import { useWeb3React } from '../../hooks'
import { useTranslation } from 'react-i18next'
import { useTokenContract } from '../../hooks'
import { calculateGasMargin } from '../../utils'
import { useAddressBalance } from '../../contexts/Balances'
import { useTransactionAdder, usePendingApproval } from '../../contexts/Transactions'
import { ROUTER_ADDRESSES } from '../../constants'
const GAS_MARGIN = ethers.utils.bigNumberify(1000)
const SubCurrencySelect = styled.button`
${({ theme }) => theme.flexRowNoWrap}
padding: 4px 50px 4px 15px;
margin-right: -40px;
line-height: 0;
align-items: center;
border-radius: 2.5rem;
height: 2rem;
outline: none;
cursor: pointer;
user-select: none;
background: ${({ theme }) => theme.blue5};
border: 1px solid ${({ theme }) => theme.blue1};
color: ${({ theme }) => theme.blue1};
`
const InputRow = styled.div`
${({ theme }) => theme.flexRowNoWrap}
@ -156,8 +131,6 @@ export default function CurrencyInputPanel({
urlAddedTokens = [], // used
onTokenSelection = null,
token = null,
showUnlock = false, // used to show unlock if approval needed
disableUnlock = false,
disableTokenSelect = false,
hideBalance = false,
isExchange = false,
@ -167,59 +140,11 @@ export default function CurrencyInputPanel({
showSendWithSwap = false
}) {
const { t } = useTranslation()
const { account, chainId } = useWeb3React()
const routerAddress = ROUTER_ADDRESSES[chainId]
const { account } = useWeb3React()
const addTransaction = useTransactionAdder()
const [modalOpen, setModalOpen] = useState(false)
const userTokenBalance = useAddressBalance(account, token)
const tokenContract = useTokenContract(token?.address)
const pendingApproval = usePendingApproval(token?.address)
function renderUnlockButton() {
if (
disableUnlock ||
!showUnlock ||
token?.address === 'ETH' ||
token?.address === WETH[chainId].address ||
!token?.address
) {
return null
} else {
if (!pendingApproval) {
return (
<SubCurrencySelect
onClick={async () => {
let estimatedGas
let useUserBalance = false
estimatedGas = await tokenContract.estimate
.approve(routerAddress, ethers.constants.MaxUint256)
.catch(e => {
console.log('Error setting max token approval.')
})
if (!estimatedGas) {
// general fallback for tokens who restrict approval amounts
estimatedGas = await tokenContract.estimate.approve(routerAddress, userTokenBalance)
useUserBalance = true
}
tokenContract
.approve(routerAddress, useUserBalance ? userTokenBalance : ethers.constants.MaxUint256, {
gasLimit: calculateGasMargin(estimatedGas, GAS_MARGIN)
})
.then(response => {
addTransaction(response, { approval: token?.address })
})
}}
>
{t('unlock')}
</SubCurrencySelect>
)
} else {
return <SubCurrencySelect>{t('pending')}</SubCurrencySelect>
}
}
}
return (
<InputPanel>
@ -234,7 +159,7 @@ export default function CurrencyInputPanel({
}}
/>
{!!token?.address && !atMax && <StyledBalanceMax onClick={onMax}>MAX</StyledBalanceMax>}
{renderUnlockButton()}
{/* {renderUnlockButton()} */}
</>
)}
<CurrencySelect
@ -288,6 +213,7 @@ export default function CurrencyInputPanel({
field={field}
onTokenSelect={onTokenSelection}
showSendWithSwap={showSendWithSwap}
hiddenToken={token?.address}
/>
)}
</InputPanel>

@ -16,29 +16,31 @@ import { Link } from '../../theme/components'
import { Text } from 'rebass'
import { TYPE } from '../../theme'
import { ArrowDown, ArrowUp } from 'react-feather'
import { GreyCard, BlueCard } from '../../components/Card'
import { GreyCard, BlueCard, YellowCard } from '../../components/Card'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import { ButtonPrimary, ButtonError } from '../Button'
import { RowBetween, RowFixed, AutoRow } from '../../components/Row'
import { ButtonPrimary, ButtonError, ButtonLight } from '../Button'
import { usePair } from '../../contexts/Pairs'
import { useToken } from '../../contexts/Tokens'
import { usePopups } from '../../contexts/Application'
import { useRoute } from '../../contexts/Routes'
import { useTransactionAdder } from '../../contexts/Transactions'
// import { useTranslation } from 'react-i18next'
import { useTransactionAdder, usePendingApproval } from '../../contexts/Transactions'
import { useAddressAllowance } from '../../contexts/Allowances'
import { useWeb3React, useTokenContract } from '../../hooks'
import { useAddressBalance, useAllBalances } from '../../contexts/Balances'
import { INITIAL_TOKENS_CONTEXT } from '../../contexts/Tokens'
import { ROUTER_ADDRESSES } from '../../constants'
import { getRouterContract, calculateGasMargin, getProviderOrSigner } from '../../utils'
import { getRouterContract, calculateGasMargin, getProviderOrSigner, getEtherscanLink } from '../../utils'
const Wrapper = styled.div`
position: relative;
`
const ArrowWrapper = styled.div`
padding: 4px;
padding: 6px;
border: 1px solid ${({ theme }) => theme.blue4};
border-radius: 12px;
display: flex;
@ -221,6 +223,9 @@ const ALLOWED_SLIPPAGE_MEDIUM = 100
const ALLOWED_SLIPPAGE_HIGH = 500
function ExchangePage({ sendingInput = false, history }) {
// text translation
// const { t } = useTranslation()
const { chainId, account, library } = useWeb3React()
const routerAddress: string = ROUTER_ADDRESSES[chainId]
@ -245,8 +250,27 @@ function ExchangePage({ sendingInput = false, history }) {
[Field.OUTPUT]: useToken(fieldData[Field.OUTPUT].address)
}
// token contracts for approvals and direct sends
const tokenContractInput: ethers.Contract = useTokenContract(tokens[Field.INPUT]?.address)
const tokenContractOutput: ethers.Contract = useTokenContract(tokens[Field.OUTPUT]?.address)
// check on pending approvals for token amounts
const pendingApprovalInput = usePendingApproval(tokens[Field.INPUT]?.address)
const pendingApprovalOutput = usePendingApproval(tokens[Field.OUTPUT]?.address)
// check for imported tokens to show warning
const importedTokenInput = tokens[Field.INPUT] && !!!INITIAL_TOKENS_CONTEXT?.[chainId]?.[tokens[Field.INPUT]?.address]
const importedTokenOutput =
tokens[Field.OUTPUT] && !!!INITIAL_TOKENS_CONTEXT?.[chainId]?.[tokens[Field.OUTPUT]?.address]
const pair: Pair = usePair(tokens[Field.INPUT], tokens[Field.OUTPUT])
// console.log(pair?.token0?.symbol)
// console.log(pair?.token1?.symbol)
// console.log('--------------')
const route = useRoute(tokens[Field.INPUT], tokens[Field.OUTPUT])
// const route = useRoute(pair)
const noRoute: boolean = !route && !!tokens[Field.INPUT] && !!tokens[Field.OUTPUT]
const emptyReserves = pair && JSBI.equal(JSBI.BigInt(0), pair.reserve0.raw)
@ -274,8 +298,6 @@ function ExchangePage({ sendingInput = false, history }) {
[Field.OUTPUT]: useAddressBalance(account, tokens[Field.OUTPUT])
}
// console.log(userBalances[Field.OUTPUT]?.raw.toString())
const parsedAmounts: { [field: number]: TokenAmount } = {}
if (typedValue !== '' && typedValue !== '.' && tokens[independentField]) {
try {
@ -344,6 +366,13 @@ function ExchangePage({ sendingInput = false, history }) {
})
}, [])
// reset field if sending with with swap is cancled
useEffect(() => {
if (sending && !sendingWithSwap) {
onTokenSelection(Field.OUTPUT, null)
}
}, [onTokenSelection, sending, sendingWithSwap])
const MIN_ETHER: TokenAmount = chainId && new TokenAmount(WETH[chainId], JSBI.BigInt(parseEther('.01')))
let maxAmountInput: TokenAmount
@ -427,8 +456,6 @@ function ExchangePage({ sendingInput = false, history }) {
outputApproval &&
JSBI.greaterThan(parsedAmounts[Field.OUTPUT].raw, outputApproval.raw)
const tokenContract: ethers.Contract = useTokenContract(tokens[Field.INPUT]?.address)
// function for a pure send
async function onSend() {
setAttemptingTxn(true)
@ -454,8 +481,8 @@ function ExchangePage({ sendingInput = false, history }) {
setShowConfirm(false)
})
} else {
estimate = tokenContract.estimate.transfer
method = tokenContract.transfer
estimate = tokenContractInput.estimate.transfer
method = tokenContractInput.transfer
args = [recipient, parsedAmounts[Field.INPUT].raw.toString()]
value = ethers.constants.Zero
const estimatedGasLimit = await estimate(...args, { value }).catch(e => {
@ -592,6 +619,28 @@ function ExchangePage({ sendingInput = false, history }) {
})
}
async function approveAmount(field) {
let estimatedGas
let useUserBalance = false
const tokenContract = field === Field.INPUT ? tokenContractInput : tokenContractOutput
estimatedGas = await tokenContract.estimate.approve(routerAddress, ethers.constants.MaxUint256).catch(e => {
console.log('Error setting max token approval.')
})
if (!estimatedGas) {
// general fallback for tokens who restrict approval amounts
estimatedGas = await tokenContract.estimate.approve(routerAddress, userBalances[field])
useUserBalance = true
}
tokenContract
.approve(routerAddress, useUserBalance ? userBalances[field] : ethers.constants.MaxUint256, {
gasLimit: calculateGasMargin(estimatedGas, GAS_MARGIN)
})
.then(response => {
addTransaction(response, { approval: tokens[field]?.address })
})
}
// errors
const [generalError, setGeneralError] = useState<string>('')
const [inputError, setInputError] = useState<string>('')
@ -806,7 +855,7 @@ function ExchangePage({ sendingInput = false, history }) {
)}
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
Slippage <Link onClick={() => setShowAdvanced(true)}>(edit limits)</Link>
Slippage
</Text>
<ErrorText warningHigh={warningHigh} fontWeight={500}>
{slippageFromTrade && slippageFromTrade.toFixed(4)}%
@ -909,7 +958,6 @@ function ExchangePage({ sendingInput = false, history }) {
onTokenSelection={address => _onTokenSelect(address)}
error={inputError}
pair={pair}
showUnlock={showInputUnlock}
hideBalance={true}
hideInput={true}
showSendWithSwap={true}
@ -928,19 +976,30 @@ function ExchangePage({ sendingInput = false, history }) {
token={tokens[Field.INPUT]}
error={inputError}
pair={pair}
showUnlock={showInputUnlock}
onUserInput={onUserInput}
onMax={() => {
maxAmountInput && onMaxInput(maxAmountInput.toExact())
}}
onTokenSelection={address => onTokenSelection(Field.INPUT, address)}
/>
<ColumnCenter>
<ArrowWrapper onClick={onSwapTokens}>
<ArrowDown size="16" color="#2F80ED" />
<ArrowUp size="16" color="#2F80ED" />
</ArrowWrapper>
</ColumnCenter>
{sendingWithSwap ? (
<RowBetween>
<ArrowWrapper onClick={onSwapTokens}>
<ArrowDown size="16" color="#2F80ED" />
<ArrowUp size="16" color="#2F80ED" />
</ArrowWrapper>
<ArrowWrapper onClick={() => setSendingWithSwap(false)} style={{ marginRight: '20px' }}>
<TYPE.blue>Remove Swap</TYPE.blue>
</ArrowWrapper>
</RowBetween>
) : (
<ColumnCenter>
<ArrowWrapper onClick={onSwapTokens}>
<ArrowDown size="16" color="#2F80ED" />
<ArrowUp size="16" color="#2F80ED" />
</ArrowWrapper>
</ColumnCenter>
)}
<CurrencyInputPanel
field={Field.OUTPUT}
value={formattedAmounts[Field.OUTPUT]}
@ -953,7 +1012,6 @@ function ExchangePage({ sendingInput = false, history }) {
onTokenSelection={address => onTokenSelection(Field.OUTPUT, address)}
error={outputError}
pair={pair}
showUnlock={showOutputUnlock}
/>
{!noRoute && ( // hide price if new exchange
<RowBetween>
@ -962,8 +1020,8 @@ function ExchangePage({ sendingInput = false, history }) {
</Text>
<Text fontWeight={500} color="#565A69">
{pair
? `1 ${tokens[Field.INPUT].symbol} = ${route?.midPrice.toSignificant(6)} ${
tokens[Field.OUTPUT].symbol
? `1 ${tokens[Field.INPUT]?.symbol} = ${route?.midPrice.toSignificant(6)} ${
tokens[Field.OUTPUT]?.symbol
}`
: '-'}
</Text>
@ -1009,6 +1067,26 @@ function ExchangePage({ sendingInput = false, history }) {
Create one now
</Link>
</RowBetween>
) : showOutputUnlock ? (
<ButtonLight
onClick={() => {
!pendingApprovalOutput && approveAmount(Field.OUTPUT)
}}
disabled={pendingApprovalOutput}
>
{pendingApprovalOutput ? 'Waiting for unlock' : 'Unlock ' + tokens[Field.OUTPUT]?.symbol}
</ButtonLight>
) : showInputUnlock ? (
<ButtonLight
onClick={() => {
approveAmount(Field.INPUT)
}}
disabled={pendingApprovalInput}
>
{!pendingApprovalInput && pendingApprovalInput
? 'Waiting for unlock'
: 'Unlock ' + tokens[Field.INPUT]?.symbol}
</ButtonLight>
) : (
<ButtonError
onClick={() => {
@ -1039,23 +1117,58 @@ function ExchangePage({ sendingInput = false, history }) {
</ButtonError>
)}
</AutoColumn>
{warningHigh && (
<FixedBottom>
<GreyCard>
<AutoColumn gap="12px">
<RowBetween>
<Text fontWeight={500}>Slippage Warning</Text>
<QuestionHelper text="" />
</RowBetween>
<Text color="#565A69" lineHeight="145.23%;">
This trade will move the price by {slippageFromTrade.toFixed(2)}%. This pool probably doesnt have
enough liquidity. Are you sure you want to continue this trade?
</Text>
</AutoColumn>
</GreyCard>
</FixedBottom>
)}
<FixedBottom>
<AutoColumn gap="20px">
{importedTokenInput && (
<YellowCard>
<AutoColumn gap="10px">
<TYPE.mediumHeader>Token imported via address</TYPE.mediumHeader>
<AutoRow gap="4px">
<TokenLogo address={tokens[Field.INPUT]?.address || ''} />
<TYPE.body>({tokens[Field.INPUT]?.symbol})</TYPE.body>
<Link href={getEtherscanLink(chainId, tokens[Field.INPUT]?.address, 'address')}>
(View on Etherscan)
</Link>
</AutoRow>
<TYPE.subHeader>
Please verify the legitimacy of this token before making any transactions.
</TYPE.subHeader>
</AutoColumn>
</YellowCard>
)}
{importedTokenOutput && (
<YellowCard>
<AutoColumn gap="10px">
<TYPE.mediumHeader>Token imported via address</TYPE.mediumHeader>
<AutoRow gap="4px">
<TokenLogo address={tokens[Field.OUTPUT]?.address || ''} />
<TYPE.body>({tokens[Field.OUTPUT]?.symbol})</TYPE.body>
<Link href={getEtherscanLink(chainId, tokens[Field.OUTPUT]?.address, 'address')}>
(View on Etherscan)
</Link>
</AutoRow>
<TYPE.subHeader>
Please verify the legitimacy of this token before making any transactions.
</TYPE.subHeader>
</AutoColumn>
</YellowCard>
)}
{warningHigh && (
<GreyCard>
<AutoColumn gap="12px">
<RowBetween>
<Text fontWeight={500}>Slippage Warning</Text>
<QuestionHelper text="" />
</RowBetween>
<Text color="#565A69" lineHeight="145.23%;">
This trade will move the price by {slippageFromTrade.toFixed(2)}%. This pool probably doesnt have
enough liquidity. Are you sure you want to continue this trade?
</Text>
</AutoColumn>
</GreyCard>
)}
</AutoColumn>
</FixedBottom>
</Wrapper>
)
}

@ -4,7 +4,7 @@ import styled from 'styled-components'
import Row from '../Row'
import Menu from '../Menu'
import Logo from '../../assets/svg/logo.svg'
import Card from '../Card'
import Card, { YellowCard } from '../Card'
import Web3Status from '../Web3Status'
import { X } from 'react-feather'
import { Link } from '../../theme'
@ -59,6 +59,7 @@ const AccountElement = styled.div`
border: 1px solid ${({ theme }) => theme.bg3};
border-radius: 8px;
padding-left: ${({ active }) => (active ? '8px' : 0)};
white-space: nowrap;
:focus {
border: 1px solid blue;
@ -119,6 +120,26 @@ export default function Header() {
)}
<Web3Status onClick={toggleWalletModal} />
</AccountElement>
{chainId === 4 && (
<YellowCard style={{ width: 'fit-content', marginLeft: '10px' }} padding={'6px'}>
Rinkeby Testnet
</YellowCard>
)}
{chainId === 3 && (
<YellowCard style={{ width: 'fit-content', marginLeft: '10px' }} padding={'6px'}>
Ropsten Testnet
</YellowCard>
)}
{chainId === 5 && (
<YellowCard style={{ width: 'fit-content', marginLeft: '10px' }} padding={'6px'}>
Goerli Testnet
</YellowCard>
)}
{chainId === 42 && (
<YellowCard style={{ width: 'fit-content', marginLeft: '10px' }} padding={'6px'}>
Kovan Testnet
</YellowCard>
)}
<Menu />
</HeaderElement>
<FixedPopupColumn gap="20px">

@ -66,7 +66,7 @@ const StyledDialogContent = styled(FilteredDialogContent)`
min-height: ${minHeight}vh;
`}
display: flex;
overflow: hidden;
/* overflow: hidden; */
border-radius: 10px;
${({ theme }) => theme.mediaWidth.upToMedium`
width: 65vw;

@ -22,9 +22,9 @@ const tabOrder = [
regex: /\/send/
},
{
path: '/supply',
path: '/pool',
textKey: 'pool',
regex: /\/supply/
regex: /\/pool/
}
]
@ -105,7 +105,7 @@ function NavigationTabs({ location: { pathname }, history }) {
{adding || removing ? (
<Tabs>
<RowBetween style={{ padding: '1rem' }}>
<HistoryLink to="/supply">
<HistoryLink to="/pool">
<ArrowLink />
</HistoryLink>
<ActiveText>{adding ? 'Add' : 'Remove'} Liquidity</ActiveText>
@ -115,7 +115,7 @@ function NavigationTabs({ location: { pathname }, history }) {
) : finding ? (
<Tabs>
<RowBetween style={{ padding: '1rem' }}>
<HistoryLink to="/supply">
<HistoryLink to="/pool">
<ArrowLink />
</HistoryLink>
<ActiveText>Find a Pool</ActiveText>

@ -1,5 +1,6 @@
import React from 'react'
import React, { useState } from 'react'
import styled from 'styled-components'
import { darken } from 'polished'
import { withRouter } from 'react-router-dom'
import { Percent, Pair } from '@uniswap/sdk'
@ -13,6 +14,7 @@ import DoubleLogo from '../DoubleLogo'
import { Text } from 'rebass'
import { GreyCard } from '../../components/Card'
import { AutoColumn } from '../Column'
import { ChevronDown, ChevronUp } from 'react-feather'
import { ButtonSecondary } from '../Button'
import { RowBetween, RowFixed } from '../Row'
@ -20,12 +22,22 @@ const FixedHeightRow = styled(RowBetween)`
height: 24px;
`
const HoverCard = styled(Card)`
border: 1px solid ${({ theme }) => theme.bg3};
:hover {
cursor: pointer;
border: 1px solid ${({ theme }) => darken(0.06, theme.bg3)};
}
`
function PositionCard({ pairAddress, token0, token1, history, minimal = false, ...rest }) {
const { account } = useWeb3React()
const allBalances = useAllBalances()
const tokenAmount0 = allBalances?.[pairAddress]?.[token0.address]
const tokenAmount1 = allBalances?.[pairAddress]?.[token1.address]
const [showMore, setShowMore] = useState(false)
const tokenAmount0 = allBalances?.[pairAddress]?.[token0?.address]
const tokenAmount1 = allBalances?.[pairAddress]?.[token1?.address]
const pair = tokenAmount0 && tokenAmount1 && new Pair(tokenAmount0, tokenAmount1)
@ -46,97 +58,154 @@ function PositionCard({ pairAddress, token0, token1, history, minimal = false, .
userPoolBalance &&
pair.getLiquidityValue(token1, totalPoolTokens, userPoolBalance, false)
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>
<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">
if (minimal) {
return (
<GreyCard {...rest}>
<AutoColumn gap="20px">
<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>
) : (
'-'
)}
<RowFixed>
<Text fontWeight={500} fontSize={20}>
Current Position
</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 onClick={() => setShowMore(!showMore)}>
<RowFixed>
<DoubleLogo a0={token0?.address || ''} a1={token1?.address || ''} margin={true} size={24} />
<Text fontWeight={500} fontSize={20}>
{token0?.symbol}:{token1?.symbol}
</Text>
</RowFixed>
<RowFixed>
<Text fontWeight={500} fontSize={20}>
{userPoolBalance ? userPoolBalance.toFixed(6) : '-'}
</Text>
</RowFixed>
</FixedHeightRow>
{!minimal && (
<AutoColumn gap="12px">
<FixedHeightRow>
<Text color="#888D9B" fontSize={16} fontWeight={500}>
Your pool share:
</Text>
<Text color="#888D9B" fontSize={16} fontWeight={500}>
{poolTokenPercentage ? poolTokenPercentage.toFixed(2) + '%' : '-'}
{token0?.symbol}:
</Text>
{token0Deposited ? (
<RowFixed>
{!minimal && <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}:
</Text>
{token1Deposited ? (
<RowFixed>
{!minimal && <TokenLogo address={token1?.address || ''} />}
<Text color="#888D9B" fontSize={16} fontWeight={500} marginLeft={'6px'}>
{token1Deposited?.toFixed(8)}
</Text>
</RowFixed>
) : (
'-'
)}
</FixedHeightRow>
</AutoColumn>
</AutoColumn>
</GreyCard>
)
} else
return (
<HoverCard {...rest} onClick={() => setShowMore(!showMore)}>
<AutoColumn gap="20px">
<FixedHeightRow>
<RowFixed>
<DoubleLogo a0={token0?.address || ''} a1={token1?.address || ''} margin={true} size={24} />
<Text fontWeight={500} fontSize={20}>
{token0?.symbol}:{token1?.symbol}
</Text>
</RowFixed>
<RowFixed>
<Text fontWeight={500} fontSize={20}>
{userPoolBalance ? userPoolBalance.toFixed(6) : '-'}
</Text>
{showMore ? (
<ChevronUp size="30" style={{ marginLeft: '10px' }} />
) : (
<ChevronDown size="30" style={{ marginLeft: '10px' }} />
)}
</RowFixed>
</FixedHeightRow>
{showMore && (
<AutoColumn gap="12px">
<FixedHeightRow>
<Text color="#888D9B" fontSize={16} fontWeight={500}>
{token0?.symbol}:
</Text>
{token0Deposited ? (
<RowFixed>
{!minimal && <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}:
</Text>
{token1Deposited ? (
<RowFixed>
{!minimal && <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>
)}
{showMore && (
<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>
{!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>
)
</HoverCard>
)
}
export default withRouter(PositionCard)

@ -105,10 +105,11 @@ const PaddedItem = styled(RowBetween)`
`
const MenuItem = styled(PaddedItem)`
cursor: pointer;
cursor: ${({ disabled }) => !disabled && 'pointer'};
:hover {
background-color: ${({ theme }) => theme.bg2};
background-color: ${({ theme, disabled }) => !disabled && theme.bg2};
}
opacity: ${({ disabled }) => (disabled ? 0.5 : 1)};
`
// filters on results
const FILTERS = {
@ -132,6 +133,7 @@ function SearchModal({
const allTokens = useAllTokens()
const allPairs = useAllPairs()
const allBalances = useAllBalances()
const [searchQuery, setSearchQuery] = useState('')
@ -175,9 +177,6 @@ function SearchModal({
}
})
.map(k => {
if (k === hiddenToken) {
return false
}
return {
name: allTokens[k].name,
symbol: allTokens[k].symbol,
@ -185,7 +184,7 @@ function SearchModal({
balance: allBalances?.[account]?.[k]
}
})
}, [allTokens, allBalances, account, sortDirection, hiddenToken])
}, [allTokens, allBalances, account, sortDirection])
const filteredTokenList = useMemo(() => {
return tokenList.filter(tokenEntry => {
@ -350,7 +349,17 @@ function SearchModal({
const zeroBalance = balance && JSBI.equal(JSBI.BigInt(0), balance.raw)
return (
<MenuItem key={address} onClick={() => (zeroBalance ? _onTokenSelect(address, true) : _onTokenSelect(address))}>
<MenuItem
key={address}
onClick={() =>
hiddenToken && hiddenToken === address
? () => {}
: zeroBalance
? _onTokenSelect(address, true)
: _onTokenSelect(address)
}
disabled={hiddenToken && hiddenToken === address}
>
<RowFixed>
<TokenLogo address={address} size={'24px'} style={{ marginRight: '14px' }} />
<Column>
@ -428,7 +437,7 @@ function SearchModal({
</RowBetween>
<Input
type={'text'}
placeholder={'Search name or address'}
placeholder={t('tokenSearchPlaceholder')}
value={searchQuery}
ref={inputRef}
onChange={onInput}
@ -447,6 +456,7 @@ function SearchModal({
</StyledLink>
</Text>
)}
{filterType === 'tokens' && <Text>Token Symbol</Text>}
</div>
<div />
<Filter title="Your Balances" filter={FILTERS.BALANCES} />

@ -0,0 +1,29 @@
import React from 'react'
import { Link } from '../../theme/components'
import { TYPE } from '../../theme'
import { AutoColumn } from '../Column'
import { useWeb3React } from '../../hooks'
import { getEtherscanLink } from '../../utils'
export default function TxnPopup({ hash, success }) {
const { chainId } = useWeb3React()
if (success) {
return (
<AutoColumn gap="12px">
<TYPE.body>Transaction Confirmed</TYPE.body>
<TYPE.green>Hash: {hash.slice(0, 8) + '...' + hash.slice(58, 65)}</TYPE.green>
<Link href={getEtherscanLink(chainId, hash, 'transaction')}>View on Etherscan</Link>
</AutoColumn>
)
} else {
return (
<AutoColumn gap="12px">
<TYPE.body>Transaction Failed</TYPE.body>
<TYPE.green>Hash: {hash.slice(0, 8) + '...' + hash.slice(58, 65)}</TYPE.green>
<Link href={getEtherscanLink(chainId, hash, 'transaction')}>View on Etherscan</Link>
</AutoColumn>
)
}
}

@ -2,7 +2,7 @@ import React from 'react'
import styled, { css } from 'styled-components'
import { useTranslation } from 'react-i18next'
import { useWeb3React, UnsupportedChainIdError } from '@web3-react/core'
import { darken, transparentize } from 'polished'
import { darken } from 'polished'
import { Activity } from 'react-feather'
import { shortenAddress } from '../../utils'
@ -10,8 +10,6 @@ import { useENSName } from '../../hooks'
import WalletModal from '../WalletModal'
import { useAllTransactions } from '../../contexts/Transactions'
import { useWalletModalToggle } from '../../contexts/Application'
import { Spinner } from '../../theme'
import Circle from '../../assets/images/circle.svg'
import { injected, walletconnect, walletlink, fortmatic, portis } from '../../connectors'
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
@ -46,39 +44,40 @@ const Web3StatusError = styled(Web3StatusGeneric)`
`
const Web3StatusConnect = styled(Web3StatusGeneric)`
background-color: transparent;
border: 1px solid ${({ theme }) => theme.blue1};
background-color: ${({ theme }) => theme.blue4};
border: 1px solid ${({ theme }) => theme.blue4};
border: none;
color: ${({ theme }) => theme.blue1};
font-weight: 500;
:hover,
:focus {
border: 1px solid ${({ theme }) => darken(0.1, theme.blue1)};
border: 1px solid ${({ theme }) => darken(0.1, theme.blue4)};
color: ${({ theme }) => darken(0.1, theme.blue1)};
}
${({ faded }) =>
faded &&
css`
background-color: transparent;
border: 1px solid ${({ theme }) => theme.blue1};
background-color: ${({ theme }) => theme.blue5};
border: 1px solid ${({ theme }) => theme.blue5};
color: ${({ theme }) => theme.blue1};
:hover,
:focus {
border: 1px solid ${({ theme }) => darken(0.1, theme.blue1)};
border: 1px solid ${({ theme }) => darken(0.1, theme.blue4)};
color: ${({ theme }) => darken(0.1, theme.blue1)};
}
`}
`
const Web3StatusConnected = styled(Web3StatusGeneric)`
background-color: ${({ pending, theme }) => (pending ? theme.blue5 : theme.bg1)};
background-color: ${({ pending, theme }) => (pending ? theme.blue1 : theme.bg1)};
border: 1px solid ${({ pending, theme }) => (pending ? theme.blue1 : theme.bg3)};
color: ${({ pending, theme }) => (pending ? theme.blue1 : theme.text3)};
color: ${({ pending, theme }) => (pending ? theme.white : theme.text3)};
font-weight: 400;
:hover {
background-color: ${({ pending, theme }) => (pending ? transparentize(0.9, theme.blue1) : darken(0.05, theme.bg1))};
background-color: ${({ pending, theme }) => (pending ? darken(0.05, theme.blue1) : darken(0.05, theme.bg1))};
:focus {
border: 1px solid ${({ pending, theme }) => (pending ? darken(0.1, theme.blue1) : darken(0.1, theme.bg3))};
@ -93,6 +92,8 @@ const Text = styled.p`
white-space: nowrap;
margin: 0 0.5rem 0 0.25rem;
font-size: 0.83rem;
width: fit-content;
font-weight: 500;
`
const NetworkIcon = styled(Activity)`
@ -102,10 +103,6 @@ const NetworkIcon = styled(Activity)`
height: 16px;
`
const SpinnerWrapper = styled(Spinner)`
margin: 0 0.25rem 0 0.25rem;
`
const IconWrapper = styled.div`
${({ theme }) => theme.flexColumnNoWrap};
align-items: center;
@ -166,9 +163,12 @@ export default function Web3Status() {
if (account) {
return (
<Web3StatusConnected onClick={toggleWalletModal} pending={hasPendingTransactions}>
{hasPendingTransactions && <SpinnerWrapper src={Circle} alt="loader" />}
<Text>{ENSName || shortenAddress(account)}</Text>
{getStatusIcon()}
{hasPendingTransactions ? (
<Text>{pending?.length} Pending</Text>
) : (
<Text>{ENSName || shortenAddress(account)}</Text>
)}
{!hasPendingTransactions && getStatusIcon()}
</Web3StatusConnected>
)
} else if (error) {

@ -5,6 +5,7 @@ import { INITIAL_TOKENS_CONTEXT } from './Tokens'
import { ChainId, WETH, Token, TokenAmount, Pair, JSBI } from '@uniswap/sdk'
const UPDATE = 'UPDATE'
const UPDATE_PAIR_ENTITY = 'UPDATE_PAIR_ENTITY'
const ALL_PAIRS: [Token, Token][] = [
[
@ -12,8 +13,8 @@ const ALL_PAIRS: [Token, Token][] = [
INITIAL_TOKENS_CONTEXT[ChainId.RINKEBY]['0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735'] //dai
],
[
INITIAL_TOKENS_CONTEXT[ChainId.RINKEBY]['0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735'],
INITIAL_TOKENS_CONTEXT[ChainId.RINKEBY]['0x8ab15C890E5C03B5F240f2D146e3DF54bEf3Df44']
INITIAL_TOKENS_CONTEXT[ChainId.RINKEBY]['0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735'], // dai
INITIAL_TOKENS_CONTEXT[ChainId.RINKEBY]['0x8ab15C890E5C03B5F240f2D146e3DF54bEf3Df44'] // mkr
]
]
@ -22,16 +23,20 @@ const PAIR_MAP: {
} = ALL_PAIRS.reduce((pairMap, [tokenA, tokenB]) => {
const tokens: [Token, Token] = tokenA?.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA]
// ensure exchanges are unique
if (pairMap?.[tokens[0].chainId]?.[tokens[0].address]?.[tokens[1].address] !== undefined)
if (pairMap?.[tokens[0].chainId]?.[tokens[0].address]?.[tokens[1].address]?.address !== undefined)
throw Error(`Duplicate exchange: ${tokenA} ${tokenB}`)
return {
...pairMap,
[tokens[0].chainId]: {
...pairMap?.[tokens[0].chainId],
[tokens[0].address]: {
...pairMap?.[tokens[0].chainId]?.[tokens[0].address],
[tokens[1].address]: Pair.getAddress(...tokens)
}
addresses: {
...pairMap?.[tokens[0].chainId]?.['addresses'],
[tokens[0].address]: {
...pairMap?.[tokens[0].chainId]?.[tokens[0].address],
[tokens[1].address]: Pair.getAddress(...tokens)
}
},
entities: {}
}
}
}, {})
@ -53,9 +58,25 @@ function reducer(state, { type, payload }) {
...state,
[tokensSorted[0].chainId]: {
...state?.[tokensSorted[0].chainId],
[tokensSorted[0].address]: {
...state?.[tokensSorted[0].chainId]?.[tokensSorted[0].address],
[tokensSorted[1].address]: Pair.getAddress(tokensSorted[0], tokensSorted[1])
addresses: {
...state?.[tokensSorted[0].chainId]['addresses'],
[tokensSorted[0].address]: {
...state?.[tokensSorted[0].chainId]?.[tokensSorted[0].address],
[tokensSorted[1].address]: Pair.getAddress(tokensSorted[0], tokensSorted[1])
}
}
}
}
}
case UPDATE_PAIR_ENTITY: {
const { pairAddress, pair, chainId } = payload
return {
...state,
[chainId]: {
...state?.[chainId],
entities: {
...state?.[chainId]?.['entities'],
[pairAddress]: pair
}
}
}
@ -73,8 +94,16 @@ export default function Provider({ children }) {
dispatch({ type: UPDATE, payload: { chainId, tokens } })
}, [])
const updatePairEntity = useCallback((pairAddress, pair, chainId) => {
dispatch({ type: UPDATE_PAIR_ENTITY, payload: { pairAddress, pair, chainId } })
}, [])
return (
<PairContext.Provider value={useMemo(() => [state, { update }], [state, update])}>{children}</PairContext.Provider>
<PairContext.Provider
value={useMemo(() => [state, { update, updatePairEntity }], [state, update, updatePairEntity])}
>
{children}
</PairContext.Provider>
)
}
@ -84,7 +113,7 @@ export function usePairAddress(tokenA?: Token, tokenB?: Token): string | undefin
const tokens: [Token, Token] = tokenA && tokenB && tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA]
const address = state?.[chainId]?.[tokens[0]?.address]?.[tokens[1]?.address]
const address = state?.[chainId]?.['addresses']?.[tokens[0]?.address]?.[tokens[1]?.address]
useEffect(() => {
if (address === undefined && tokenA && tokenB) {
@ -97,36 +126,29 @@ export function usePairAddress(tokenA?: Token, tokenB?: Token): string | undefin
}
export function usePair(tokenA?: Token, tokenB?: Token): Pair | undefined {
const { chainId } = useWeb3React()
const [state, { updatePairEntity }] = usePairContext()
const address = usePairAddress(tokenA, tokenB)
const pair = state?.[chainId]?.['entities']?.[address]
const tokenAmountA = useAddressBalance(address, tokenA)
const tokenAmountB = useAddressBalance(address, tokenB)
const [pair, setPair] = useState<Pair>()
useEffect(() => {
if (!pair && tokenAmountA && tokenAmountB) {
setPair(new Pair(tokenAmountA, tokenAmountB))
updatePairEntity(address, new Pair(tokenAmountA, tokenAmountB), chainId)
}
}, [pair, tokenAmountA, tokenAmountB])
}, [pair, tokenAmountA, tokenAmountB, address, updatePairEntity, chainId])
return useMemo(() => {
return pair
}, [pair])
}
export function useAllPairsRaw() {
const { chainId } = useWeb3React()
const [state] = usePairContext()
const allExchangeDetails = state?.[chainId]
return allExchangeDetails
return pair
}
export function useAllPairs() {
const { chainId } = useWeb3React()
const [state] = usePairContext()
const allPairDetails = state?.[chainId]
const allPairDetails = state?.[chainId]?.['addresses']
const allPairs = useMemo(() => {
if (!allPairDetails) {
@ -136,10 +158,14 @@ export function useAllPairs() {
Object.keys(allPairDetails).map(token0Address => {
return Object.keys(allPairDetails[token0Address]).map(token1Address => {
const pairAddress = allPairDetails[token0Address][token1Address]
return (formattedExchanges[pairAddress] = {
token0: token0Address,
token1: token1Address
})
if (pairAddress) {
return (formattedExchanges[pairAddress] = {
token0: token0Address,
token1: token1Address
})
} else {
return null
}
})
})
return formattedExchanges

@ -30,7 +30,7 @@ function reducer(state: RouteState, { type, payload }) {
[chainId]: {
...state[chainId],
[tokens[0]]: {
...state[tokens[0]],
...state[chainId]?.[tokens[0]],
[tokens[1]]: {
route
}
@ -77,14 +77,14 @@ export function useRoute(tokenA: Token, tokenB: Token) {
const defaultPair = usePair(tokenA, tokenB)
// get token<->WETH pairs
const aToETH = usePair(tokenA && !tokenA.equals(WETH[chainId]) ? tokenA : null, WETH[chainId])
const bToETH = usePair(tokenB && !tokenB.equals(WETH[chainId]) ? tokenB : null, WETH[chainId])
const aToETH = usePair(tokenA && chainId && !tokenA.equals(WETH[chainId]) ? tokenA : null, WETH[chainId])
const bToETH = usePair(tokenB && chainId && !tokenB.equals(WETH[chainId]) ? tokenB : null, WETH[chainId])
// needs to route through WETH
const requiresHop =
defaultPair &&
JSBI.equal(defaultPair.reserve0.raw, JSBI.BigInt(0)) &&
JSBI.equal(defaultPair.reserve1.raw, JSBI.BigInt(0))
JSBI.equal(defaultPair?.reserve0?.raw, JSBI.BigInt(0)) &&
JSBI.equal(defaultPair?.reserve1?.raw, JSBI.BigInt(0))
useEffect(() => {
if (!route && tokenA && tokenB) {

@ -6,6 +6,9 @@ import { isAddress, getTokenName, getTokenSymbol, getTokenDecimals, safeAccess }
const UPDATE = 'UPDATE'
export const ALL_TOKENS = [
//Mainnet Tokens
WETH[ChainId.MAINNET],
// Rinkeby Tokens
WETH[ChainId.RINKEBY],
new Token(ChainId.RINKEBY, '0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735', 18, 'DAI', 'Dai Stablecoin'),

@ -1,8 +1,10 @@
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
import TxnPopup from '../components/TxnPopup'
import { useWeb3React } from '../hooks'
import { safeAccess } from '../utils'
import { useBlockNumber } from './Application'
import { useBlockNumber, usePopups } from './Application'
const RESPONSE = 'response'
const CUSTOM_DATA = 'CUSTOM_DATA'
@ -110,6 +112,9 @@ export function Updater() {
const [state, { check, finalize }] = useTransactionsContext()
const allTransactions = safeAccess(state, [chainId]) || {}
// show popup on confirm
const [, addPopup] = usePopups()
useEffect(() => {
if ((chainId || chainId === 0) && library) {
let stale = false
@ -126,6 +131,7 @@ export function Updater() {
check(chainId, hash, globalBlockNumber)
} else {
finalize(chainId, hash, receipt)
addPopup(<TxnPopup hash={hash} success={true} />)
}
}
})
@ -138,7 +144,7 @@ export function Updater() {
stale = true
}
}
}, [chainId, library, allTransactions, globalBlockNumber, check, finalize])
}, [chainId, library, allTransactions, globalBlockNumber, check, finalize, addPopup])
return null
}

@ -1,12 +1,16 @@
import React, { Suspense, lazy } from 'react'
import React, { Suspense, lazy, useState } from 'react'
import styled from 'styled-components'
import { transparentize } from 'polished'
import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom'
import { Text } from 'rebass'
import Header from '../components/Header'
import NavigationTabs from '../components/NavigationTabs'
import Web3ReactManager from '../components/Web3ReactManager'
import { useWeb3React } from '../hooks'
import { TYPE } from '../theme'
import { Hover } from '../theme/components'
import { AutoColumn } from '../components/Column'
import { PinkCard } from '../components/Card'
import { ButtonPink } from '../components/Button'
import { isAddress, getAllQueryParams } from '../utils'
const Swap = lazy(() => import('./Swap'))
@ -29,29 +33,6 @@ const HeaderWrapper = styled.div`
justify-content: space-between;
`
const BetaMessage = styled.div`
${({ theme }) => theme.flexRowNoWrap}
cursor: pointer;
max-height: 40px;
flex: 1 0 auto;
align-items: center;
position: relative;
padding: 0.5rem 1rem;
margin-bottom: 1rem;
border: 1px solid ${({ theme }) => transparentize(0.6, theme.pink1)};
background-color: ${({ theme }) => transparentize(0.9, theme.pink1)};
border-radius: 1rem;
font-size: 0.75rem;
line-height: 1rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: ${({ theme }) => theme.pink1};
min-width: 380px;
text-align: center;
justify-content: center;
`
const BodyWrapper = styled.div`
display: flex;
flex-direction: column;
@ -61,6 +42,12 @@ const BodyWrapper = styled.div`
flex: 1;
overflow: auto;
padding-top: 40px;
& > * {
max-width: calc(355px + 4rem);
width: 90%;
margin-bottom: 20px;
}
`
const Body = styled.div`
@ -76,7 +63,7 @@ const Body = styled.div`
export default function App() {
const params = getAllQueryParams()
const { chainId } = useWeb3React()
const [showMigrationMessage, toggleShowMigrationMessage] = useState(true)
return (
<>
@ -86,17 +73,18 @@ export default function App() {
<Header />
</HeaderWrapper>
<BodyWrapper>
{chainId === 1 && (
<BetaMessage>Incorrect network. This site is intended to be used on Ethereum testnets only.</BetaMessage>
{showMigrationMessage && (
<PinkCard padding="20px">
<AutoColumn justify={'center'} gap={'20px'}>
<TYPE.largeHeader>Uniswap has upgraded.</TYPE.largeHeader>
<Text textAlign="center">Are you a liquidity provider? Upgrade now using the migration helper.</Text>
<ButtonPink width={'265px'}>Migrate your liquidity </ButtonPink>
<Hover onClick={() => toggleShowMigrationMessage(false)}>
<Text textAlign="center">Dismiss</Text>
</Hover>
</AutoColumn>
</PinkCard>
)}
{(chainId === 3 || chainId === 4 || chainId === 5 || chainId === 42) && (
<BetaMessage>
Connected to{' '}
{chainId === 3 ? 'Ropsten ' : chainId === 4 ? 'Rinkeby' : chainId === 5 ? 'Goerli' : 'Kovan'} testnet.
</BetaMessage>
)}
<Body>
<Web3ReactManager>
<BrowserRouter>
@ -138,7 +126,7 @@ export default function App() {
}
}}
/>
<Route exaxct path={'/supply'} component={() => <Pool params={params} />} />
<Route exaxct path={'/pool'} component={() => <Pool params={params} />} />
<Route
exact
strict
@ -154,7 +142,7 @@ export default function App() {
if (t0 && t1) {
return <Add params={params} token0={t0} token1={t1} />
} else {
return <Redirect to={{ pathname: '/supply' }} />
return <Redirect to={{ pathname: '/pool' }} />
}
}}
/>
@ -173,7 +161,7 @@ export default function App() {
if (t0 && t1) {
return <Remove params={params} token0={t0} token1={t1} />
} else {
return <Redirect to={{ pathname: '/supply' }} />
return <Redirect to={{ pathname: '/pool' }} />
}
}}
/>

@ -11,23 +11,23 @@ import PositionCard from '../../components/PositionCard'
import ConfirmationModal from '../../components/ConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import { Text } from 'rebass'
import { TYPE } from '../../theme'
import { Plus } from 'react-feather'
import { ButtonPrimary } from '../../components/Button'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import { ButtonPrimary, ButtonLight } from '../../components/Button'
import Row, { RowBetween, RowFlat, RowFixed } from '../../components/Row'
import { useToken } from '../../contexts/Tokens'
import { usePopups } from '../../contexts/Application'
import { useWeb3React } from '../../hooks'
import { useAddressBalance } from '../../contexts/Balances'
import { useAddressAllowance } from '../../contexts/Allowances'
import { useTransactionAdder } from '../../contexts/Transactions'
import { usePair, useTotalSupply } from '../../contexts/Pairs'
import { useWeb3React, useTokenContract } from '../../hooks'
import { useTransactionAdder, usePendingApproval } from '../../contexts/Transactions'
import { BigNumber } from 'ethers/utils'
import { ROUTER_ADDRESSES } from '../../constants'
import { getRouterContract, calculateGasMargin } from '../../utils'
import { TYPE } from '../../theme'
// denominated in bips
const ALLOWED_SLIPPAGE = 200
@ -43,7 +43,7 @@ const Wrapper = styled.div`
const FixedBottom = styled.div`
position: absolute;
bottom: -200px;
bottom: -220px;
width: 100%;
`
@ -158,6 +158,14 @@ export default function AddLiquidity({ token0, token1 }) {
[Field.OUTPUT]: useToken(fieldData[Field.OUTPUT].address)
}
// token contracts for approvals and direct sends
const tokenContractInput: ethers.Contract = useTokenContract(tokens[Field.INPUT]?.address)
const tokenContractOutput: ethers.Contract = useTokenContract(tokens[Field.OUTPUT]?.address)
// check on pending approvals for token amounts
const pendingApprovalInput = usePendingApproval(tokens[Field.INPUT]?.address)
const pendingApprovalOutput = usePendingApproval(tokens[Field.OUTPUT]?.address)
// exhchange data
const pair: Pair = usePair(tokens[Field.INPUT], tokens[Field.OUTPUT])
const route: Route = pair ? new Route([pair], tokens[independentField]) : undefined
@ -456,6 +464,28 @@ export default function AddLiquidity({ token0, token1 }) {
})
}
async function approveAmount(field) {
let estimatedGas
let useUserBalance = false
const tokenContract = field === Field.INPUT ? tokenContractInput : tokenContractOutput
estimatedGas = await tokenContract.estimate.approve(routerAddress, ethers.constants.MaxUint256).catch(e => {
console.log('Error setting max token approval.')
})
if (!estimatedGas) {
// general fallback for tokens who restrict approval amounts
estimatedGas = await tokenContract.estimate.approve(routerAddress, userBalances[field])
useUserBalance = true
}
tokenContract
.approve(routerAddress, useUserBalance ? userBalances[field] : ethers.constants.MaxUint256, {
gasLimit: calculateGasMargin(estimatedGas, GAS_MARGIN)
})
.then(response => {
addTransaction(response, { approval: tokens[field]?.address })
})
}
const modalHeader = () => {
return (
<AutoColumn gap="20px">
@ -569,7 +599,6 @@ export default function AddLiquidity({ token0, token1 }) {
onTokenSelection={onTokenSelection}
error={inputError}
pair={pair}
showUnlock={showInputUnlock}
disableTokenSelect
/>
<ColumnCenter>
@ -587,7 +616,6 @@ export default function AddLiquidity({ token0, token1 }) {
onTokenSelection={onTokenSelection}
error={outputError}
pair={pair}
showUnlock={showOutputUnlock}
disableTokenSelect
/>
{!noLiquidity && (
@ -599,16 +627,34 @@ export default function AddLiquidity({ token0, token1 }) {
</div>
</RowBetween>
)}
<ButtonPrimary
onClick={() => {
setShowConfirm(true)
}}
disabled={!isValid}
>
<Text fontSize={20} fontWeight={500}>
{generalError ? generalError : inputError ? inputError : outputError ? outputError : 'Supply'}
</Text>
</ButtonPrimary>
{showOutputUnlock ? (
<ButtonLight
onClick={() => {
approveAmount(Field.OUTPUT)
}}
>
{pendingApprovalOutput ? 'Waiting for unlock' : 'Unlock ' + tokens[Field.OUTPUT]?.symbol}
</ButtonLight>
) : showInputUnlock ? (
<ButtonLight
onClick={() => {
approveAmount(Field.INPUT)
}}
>
{pendingApprovalInput ? 'Waiting for unlock' : 'Unlock ' + tokens[Field.INPUT]?.symbol}
</ButtonLight>
) : (
<ButtonPrimary
onClick={() => {
setShowConfirm(true)
}}
disabled={!isValid}
>
<Text fontSize={20} fontWeight={500}>
{generalError ? generalError : inputError ? inputError : outputError ? outputError : 'Supply'}
</Text>
</ButtonPrimary>
)}
{!noLiquidity && (
<FixedBottom>
<PositionCard

@ -10,6 +10,7 @@ import DoubleLogo from '../../components/DoubleLogo'
import PositionCard from '../../components/PositionCard'
import ConfirmationModal from '../../components/ConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import { TYPE } from '../../theme'
import { Text } from 'rebass'
import { LightCard } from '../../components/Card'
import { ButtonPrimary } from '../../components/Button'
@ -29,7 +30,6 @@ import { BigNumber } from 'ethers/utils'
import { splitSignature } from '@ethersproject/bytes'
import { ROUTER_ADDRESSES } from '../../constants'
import { getRouterContract, calculateGasMargin } from '../../utils'
import { TYPE } from '../../theme'
// denominated in seconds
const DEADLINE_FROM_NOW = 60 * 15
@ -42,7 +42,7 @@ const Wrapper = styled.div`
const FixedBottom = styled.div`
position: absolute;
bottom: -200px;
bottom: -220px;
width: 100%;
`

@ -3,15 +3,13 @@ import styled from 'styled-components'
import { JSBI } from '@uniswap/sdk'
import { withRouter } from 'react-router-dom'
import Card from '../../components/Card'
import Question from '../../components/Question'
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 { RowBetween } from '../../components/Row'
import { ButtonPrimary } from '../../components/Button'
import { useAllPairs } from '../../contexts/Pairs'
@ -23,11 +21,6 @@ const Positions = styled.div`
position: relative;
margin-top: 38px;
`
const FixedBottom = styled.div`
position: absolute;
bottom: -260px;
width: 100%;
`
function Supply({ history }) {
const { account } = useWeb3React()
@ -40,6 +33,8 @@ function Supply({ history }) {
// initiate listener for LP balances
useAccountLPBalances(account)
// console.log(allPairs)
const filteredExchangeList = Object.keys(allPairs)
.filter((pairAddress, i) => {
return (
@ -67,7 +62,7 @@ function Supply({ history }) {
setShowPoolSearch(true)
}}
>
<Text fontSize={20}>Join a pool</Text>
<Text fontSize={20}>Join {filteredExchangeList?.length > 0 ? 'another' : 'a'} pool</Text>
</ButtonPrimary>
<Positions>
<AutoColumn gap="20px">
@ -80,34 +75,17 @@ function Supply({ history }) {
{filteredExchangeList}
<AutoColumn justify="center">
<Text color="#AEAEAE">
{filteredExchangeList?.length !== 0 ? `Don't see your ` : 'Already have '} liquidity?{' '}
{filteredExchangeList?.length !== 0 ? `Don't see a pool you joined? ` : 'Already joined a pool?'}{' '}
<Link
onClick={() => {
history.push('/find')
}}
>
Find it now.
Find it.
</Link>
</Text>
</AutoColumn>
</AutoColumn>
<FixedBottom>
<Card bg="rgba(255, 255, 255, 0.6)" padding={'24px'}>
<AutoColumn gap="30px">
<Text fontSize="20px" fontWeight={500}>
Earn fees with pooled market making.
</Text>
<Text fontSize="12px">
<Link>Provide liquidity </Link>to earn .3% spread fees for providing market depth.
</Text>
<Link>
<Row>
Learn More <ArrowRight size="16" />
</Row>
</Link>
</AutoColumn>
</Card>
</FixedBottom>
</Positions>
<SearchModal isOpen={showPoolSearch} onDismiss={() => setShowPoolSearch(false)} />
</>

@ -71,3 +71,9 @@ export const Spinner = styled.img`
width: 16px;
height: 16px;
`
export const Hover = styled.div`
:hover {
cursor: pointer;
}
`

@ -75,6 +75,7 @@ const theme = darkMode => ({
red1: '#FF6871',
green1: '#27AE60',
yellow1: '#FFE270',
yellow2: '#F3841E',
//shadows
shadow1: darkMode ? '#000' : '#2F80ED',
@ -99,7 +100,17 @@ export const TYPE = {
</Text>
),
largeHeader: ({ children, ...rest }) => (
<Text fontWeight={600} fontSize={24} color={theme().black} {...rest}>
<Text fontWeight={600} fontSize={24} {...rest}>
{children}
</Text>
),
mediumHeader: ({ children, ...rest }) => (
<Text fontWeight={500} fontSize={20} color={theme().text1} {...rest}>
{children}
</Text>
),
subHeader: ({ children, ...rest }) => (
<Text fontWeight={400} fontSize={14} color={theme().text1} {...rest}>
{children}
</Text>
),
@ -113,6 +124,11 @@ export const TYPE = {
{children}
</Text>
),
green: ({ children, ...rest }) => (
<Text fontWeight={500} color={theme().green1} {...rest}>
{children}
</Text>
),
gray: ({ children, ...rest }) => (
<Text fontWeight={500} color={theme().bg3} {...rest}>
{children}