From 50c9d9973a1d09a52abe35b16a3345049b47fb7d Mon Sep 17 00:00:00 2001 From: Ian Lapham Date: Wed, 15 Apr 2020 20:56:59 -0400 Subject: [PATCH] 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 --- public/locales/en.json | 6 +- src/components/AddressInputPanel/index.js | 33 +-- src/components/AdvancedSettings/index.js | 8 +- src/components/Button/index.js | 20 +- src/components/Card/index.js | 18 +- src/components/ConfirmationModal/index.js | 2 + src/components/CurrencyInputPanel/index.js | 80 +------ src/components/ExchangePage/index.tsx | 193 +++++++++++++---- src/components/Header/index.js | 23 +- src/components/Modal/index.js | 2 +- src/components/NavigationTabs/index.js | 8 +- src/components/PositionCard/index.js | 239 +++++++++++++-------- src/components/SearchModal/index.js | 26 ++- src/components/TxnPopup/index.js | 29 +++ src/components/Web3Status/index.js | 38 ++-- src/contexts/Pairs.tsx | 90 +++++--- src/contexts/Routes.tsx | 10 +- src/contexts/Tokens.tsx | 3 + src/contexts/Transactions.js | 10 +- src/pages/App.js | 68 +++--- src/pages/Supply/AddLiquidity.tsx | 80 +++++-- src/pages/Supply/RemoveLiquidity.tsx | 4 +- src/pages/Supply/index.js | 34 +-- src/theme/components.js | 6 + src/theme/index.js | 18 +- 25 files changed, 662 insertions(+), 386 deletions(-) create mode 100644 src/components/TxnPopup/index.js diff --git a/public/locales/en.json b/public/locales/en.json index 7fe01f5d1f..ebfa9c2f3c 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -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" } diff --git a/src/components/AddressInputPanel/index.js b/src/components/AddressInputPanel/index.js index 2246a687dc..12715d5062 100644 --- a/src/components/AddressInputPanel/index.js +++ b/src/components/AddressInputPanel/index.js @@ -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} /> - + {/* - + */} diff --git a/src/components/AdvancedSettings/index.js b/src/components/AdvancedSettings/index.js index 5c493a6d23..07b098f453 100644 --- a/src/components/AdvancedSettings/index.js +++ b/src/components/AdvancedSettings/index.js @@ -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 - Limit additional price impact - + Limit front-running tolerance + 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)}; diff --git a/src/components/Card/index.js b/src/components/Card/index.js index 93165221a4..e6abbc9e27 100644 --- a/src/components/Card/index.js +++ b/src/components/Card/index.js @@ -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; diff --git a/src/components/ConfirmationModal/index.js b/src/components/ConfirmationModal/index.js index 5294ec5648..c75558348d 100644 --- a/src/components/ConfirmationModal/index.js +++ b/src/components/ConfirmationModal/index.js @@ -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)` diff --git a/src/components/CurrencyInputPanel/index.js b/src/components/CurrencyInputPanel/index.js index 3511437789..087377e743 100644 --- a/src/components/CurrencyInputPanel/index.js +++ b/src/components/CurrencyInputPanel/index.js @@ -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 ( - { - 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')} - - ) - } else { - return {t('pending')} - } - } - } return ( @@ -234,7 +159,7 @@ export default function CurrencyInputPanel({ }} /> {!!token?.address && !atMax && MAX} - {renderUnlockButton()} + {/* {renderUnlockButton()} */} )} )} diff --git a/src/components/ExchangePage/index.tsx b/src/components/ExchangePage/index.tsx index f6ed1a02ed..ff6d937d2b 100644 --- a/src/components/ExchangePage/index.tsx +++ b/src/components/ExchangePage/index.tsx @@ -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('') const [inputError, setInputError] = useState('') @@ -806,7 +855,7 @@ function ExchangePage({ sendingInput = false, history }) { )} - Slippage setShowAdvanced(true)}>(edit limits) + Slippage {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)} /> - - - - - - + {sendingWithSwap ? ( + + + + + + setSendingWithSwap(false)} style={{ marginRight: '20px' }}> + Remove Swap + + + ) : ( + + + + + + + )} onTokenSelection(Field.OUTPUT, address)} error={outputError} pair={pair} - showUnlock={showOutputUnlock} /> {!noRoute && ( // hide price if new exchange @@ -962,8 +1020,8 @@ function ExchangePage({ sendingInput = false, history }) { {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 }` : '-'} @@ -1009,6 +1067,26 @@ function ExchangePage({ sendingInput = false, history }) { Create one now + ) : showOutputUnlock ? ( + { + !pendingApprovalOutput && approveAmount(Field.OUTPUT) + }} + disabled={pendingApprovalOutput} + > + {pendingApprovalOutput ? 'Waiting for unlock' : 'Unlock ' + tokens[Field.OUTPUT]?.symbol} + + ) : showInputUnlock ? ( + { + approveAmount(Field.INPUT) + }} + disabled={pendingApprovalInput} + > + {!pendingApprovalInput && pendingApprovalInput + ? 'Waiting for unlock' + : 'Unlock ' + tokens[Field.INPUT]?.symbol} + ) : ( { @@ -1039,23 +1117,58 @@ function ExchangePage({ sendingInput = false, history }) { )} - - {warningHigh && ( - - - - - Slippage Warning - - - - This trade will move the price by {slippageFromTrade.toFixed(2)}%. This pool probably doesn’t have - enough liquidity. Are you sure you want to continue this trade? - - - - - )} + + + {importedTokenInput && ( + + + Token imported via address + + + ({tokens[Field.INPUT]?.symbol}) + + (View on Etherscan) + + + + Please verify the legitimacy of this token before making any transactions. + + + + )} + {importedTokenOutput && ( + + + Token imported via address + + + ({tokens[Field.OUTPUT]?.symbol}) + + (View on Etherscan) + + + + Please verify the legitimacy of this token before making any transactions. + + + + )} + {warningHigh && ( + + + + Slippage Warning + + + + This trade will move the price by {slippageFromTrade.toFixed(2)}%. This pool probably doesn’t have + enough liquidity. Are you sure you want to continue this trade? + + + + )} + + ) } diff --git a/src/components/Header/index.js b/src/components/Header/index.js index 3e32c74f0a..a0f382cc56 100644 --- a/src/components/Header/index.js +++ b/src/components/Header/index.js @@ -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() { )} + {chainId === 4 && ( + + Rinkeby Testnet + + )} + {chainId === 3 && ( + + Ropsten Testnet + + )} + {chainId === 5 && ( + + Goerli Testnet + + )} + {chainId === 42 && ( + + Kovan Testnet + + )} diff --git a/src/components/Modal/index.js b/src/components/Modal/index.js index f635c7f7b0..b83d62c83a 100644 --- a/src/components/Modal/index.js +++ b/src/components/Modal/index.js @@ -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; diff --git a/src/components/NavigationTabs/index.js b/src/components/NavigationTabs/index.js index dee57200e0..4b23faf4a6 100644 --- a/src/components/NavigationTabs/index.js +++ b/src/components/NavigationTabs/index.js @@ -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 ? ( - + {adding ? 'Add' : 'Remove'} Liquidity @@ -115,7 +115,7 @@ function NavigationTabs({ location: { pathname }, history }) { ) : finding ? ( - + Find a Pool diff --git a/src/components/PositionCard/index.js b/src/components/PositionCard/index.js index 72f12d6888..cabbbe6de6 100644 --- a/src/components/PositionCard/index.js +++ b/src/components/PositionCard/index.js @@ -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 ( - - {children} - - ) - } else { - return {children} - } - } - - return ( - - - - - - - {token0?.symbol}:{token1?.symbol} - - - - {userPoolBalance ? userPoolBalance.toFixed(6) : '-'} - - - + if (minimal) { + return ( + + - - {token0?.symbol} Deposited: - - {token0Deposited ? ( - - - - {token0Deposited?.toFixed(8)} - - - ) : ( - '-' - )} + + + Current Position + + - - - {token1?.symbol} Deposited: - - {token1Deposited ? ( - - - - {token1Deposited?.toFixed(8)} - - - ) : ( - '-' - )} + setShowMore(!showMore)}> + + + + {token0?.symbol}:{token1?.symbol} + + + + + {userPoolBalance ? userPoolBalance.toFixed(6) : '-'} + + - {!minimal && ( + - Your pool share: - - - {poolTokenPercentage ? poolTokenPercentage.toFixed(2) + '%' : '-'} + {token0?.symbol}: + {token0Deposited ? ( + + {!minimal && } + + {token0Deposited?.toFixed(8)} + + + ) : ( + '-' + )} + + + {token1?.symbol}: + + {token1Deposited ? ( + + {!minimal && } + + {token1Deposited?.toFixed(8)} + + + ) : ( + '-' + )} + + + + + ) + } else + return ( + setShowMore(!showMore)}> + + + + + + {token0?.symbol}:{token1?.symbol} + + + + + {userPoolBalance ? userPoolBalance.toFixed(6) : '-'} + + {showMore ? ( + + ) : ( + + )} + + + {showMore && ( + + + + {token0?.symbol}: + + {token0Deposited ? ( + + {!minimal && } + + {token0Deposited?.toFixed(8)} + + + ) : ( + '-' + )} + + + + {token1?.symbol}: + + {token1Deposited ? ( + + {!minimal && } + + {token1Deposited?.toFixed(8)} + + + ) : ( + '-' + )} + + {!minimal && ( + + + Your pool share: + + + {poolTokenPercentage ? poolTokenPercentage.toFixed(2) + '%' : '-'} + + + )} + + )} + {showMore && ( + + { + history.push('/add/' + token0?.address + '-' + token1?.address) + }} + > + Add + + { + history.push('/remove/' + token0?.address + '-' + token1?.address) + }} + > + Remove + + )} - {!minimal && ( - - { - history.push('/add/' + token0?.address + '-' + token1?.address) - }} - > - Add - - { - history.push('/remove/' + token0?.address + '-' + token1?.address) - }} - > - Remove - - - )} - - - ) + + ) } export default withRouter(PositionCard) diff --git a/src/components/SearchModal/index.js b/src/components/SearchModal/index.js index bad9cf18a5..66884dcabe 100644 --- a/src/components/SearchModal/index.js +++ b/src/components/SearchModal/index.js @@ -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 ( - (zeroBalance ? _onTokenSelect(address, true) : _onTokenSelect(address))}> + + hiddenToken && hiddenToken === address + ? () => {} + : zeroBalance + ? _onTokenSelect(address, true) + : _onTokenSelect(address) + } + disabled={hiddenToken && hiddenToken === address} + > @@ -428,7 +437,7 @@ function SearchModal({ )} + {filterType === 'tokens' && Token Symbol}
diff --git a/src/components/TxnPopup/index.js b/src/components/TxnPopup/index.js new file mode 100644 index 0000000000..b4ad9fec46 --- /dev/null +++ b/src/components/TxnPopup/index.js @@ -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 ( + + Transaction Confirmed + Hash: {hash.slice(0, 8) + '...' + hash.slice(58, 65)} + View on Etherscan + + ) + } else { + return ( + + Transaction Failed + Hash: {hash.slice(0, 8) + '...' + hash.slice(58, 65)} + View on Etherscan + + ) + } +} diff --git a/src/components/Web3Status/index.js b/src/components/Web3Status/index.js index a59479df86..33d3d27071 100644 --- a/src/components/Web3Status/index.js +++ b/src/components/Web3Status/index.js @@ -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 ( - {hasPendingTransactions && } - {ENSName || shortenAddress(account)} - {getStatusIcon()} + {hasPendingTransactions ? ( + {pending?.length} Pending + ) : ( + {ENSName || shortenAddress(account)} + )} + {!hasPendingTransactions && getStatusIcon()} ) } else if (error) { diff --git a/src/contexts/Pairs.tsx b/src/contexts/Pairs.tsx index d0812e1fc0..cf9e1553ee 100644 --- a/src/contexts/Pairs.tsx +++ b/src/contexts/Pairs.tsx @@ -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 ( - [state, { update }], [state, update])}>{children} + [state, { update, updatePairEntity }], [state, update, updatePairEntity])} + > + {children} + ) } @@ -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() 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 diff --git a/src/contexts/Routes.tsx b/src/contexts/Routes.tsx index 08b04794ef..07e1e4b65e 100644 --- a/src/contexts/Routes.tsx +++ b/src/contexts/Routes.tsx @@ -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) { diff --git a/src/contexts/Tokens.tsx b/src/contexts/Tokens.tsx index 65054b064e..7a2990c9c1 100644 --- a/src/contexts/Tokens.tsx +++ b/src/contexts/Tokens.tsx @@ -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'), diff --git a/src/contexts/Transactions.js b/src/contexts/Transactions.js index bb97d5435b..50094798c7 100644 --- a/src/contexts/Transactions.js +++ b/src/contexts/Transactions.js @@ -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() } } }) @@ -138,7 +144,7 @@ export function Updater() { stale = true } } - }, [chainId, library, allTransactions, globalBlockNumber, check, finalize]) + }, [chainId, library, allTransactions, globalBlockNumber, check, finalize, addPopup]) return null } diff --git a/src/pages/App.js b/src/pages/App.js index 5ed178e92e..ba2619a7e4 100644 --- a/src/pages/App.js +++ b/src/pages/App.js @@ -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() {
- {chainId === 1 && ( - Incorrect network. This site is intended to be used on Ethereum testnets only. + {showMigrationMessage && ( + + + Uniswap has upgraded. + Are you a liquidity provider? Upgrade now using the migration helper. + Migrate your liquidity + toggleShowMigrationMessage(false)}> + Dismiss + + + )} - - {(chainId === 3 || chainId === 4 || chainId === 5 || chainId === 42) && ( - - Connected to{' '} - {chainId === 3 ? 'Ropsten ' : chainId === 4 ? 'Rinkeby' : chainId === 5 ? 'Goerli' : 'Kovan'} testnet. - - )} - @@ -138,7 +126,7 @@ export default function App() { } }} /> - } /> + } /> } else { - return + return } }} /> @@ -173,7 +161,7 @@ export default function App() { if (t0 && t1) { return } else { - return + return } }} /> diff --git a/src/pages/Supply/AddLiquidity.tsx b/src/pages/Supply/AddLiquidity.tsx index 4131979c61..69f452ffb9 100644 --- a/src/pages/Supply/AddLiquidity.tsx +++ b/src/pages/Supply/AddLiquidity.tsx @@ -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 ( @@ -569,7 +599,6 @@ export default function AddLiquidity({ token0, token1 }) { onTokenSelection={onTokenSelection} error={inputError} pair={pair} - showUnlock={showInputUnlock} disableTokenSelect /> @@ -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 }) {
)} - { - setShowConfirm(true) - }} - disabled={!isValid} - > - - {generalError ? generalError : inputError ? inputError : outputError ? outputError : 'Supply'} - - + {showOutputUnlock ? ( + { + approveAmount(Field.OUTPUT) + }} + > + {pendingApprovalOutput ? 'Waiting for unlock' : 'Unlock ' + tokens[Field.OUTPUT]?.symbol} + + ) : showInputUnlock ? ( + { + approveAmount(Field.INPUT) + }} + > + {pendingApprovalInput ? 'Waiting for unlock' : 'Unlock ' + tokens[Field.INPUT]?.symbol} + + ) : ( + { + setShowConfirm(true) + }} + disabled={!isValid} + > + + {generalError ? generalError : inputError ? inputError : outputError ? outputError : 'Supply'} + + + )} {!noLiquidity && ( { return ( @@ -67,7 +62,7 @@ function Supply({ history }) { setShowPoolSearch(true) }} > - Join a pool + Join {filteredExchangeList?.length > 0 ? 'another' : 'a'} pool @@ -80,34 +75,17 @@ function Supply({ history }) { {filteredExchangeList} - {filteredExchangeList?.length !== 0 ? `Don't see your ` : 'Already have '} liquidity?{' '} + {filteredExchangeList?.length !== 0 ? `Don't see a pool you joined? ` : 'Already joined a pool?'}{' '} { history.push('/find') }} > - Find it now. + Find it. - - - - - Earn fees with pooled market making. - - - Provide liquidity to earn .3% spread fees for providing market depth. - - - - Learn More - - - - - setShowPoolSearch(false)} /> diff --git a/src/theme/components.js b/src/theme/components.js index c1ca0e9ab8..a6ae06d64f 100644 --- a/src/theme/components.js +++ b/src/theme/components.js @@ -71,3 +71,9 @@ export const Spinner = styled.img` width: 16px; height: 16px; ` + +export const Hover = styled.div` + :hover { + cursor: pointer; + } +` diff --git a/src/theme/index.js b/src/theme/index.js index 5ddc203266..0211cf5775 100644 --- a/src/theme/index.js +++ b/src/theme/index.js @@ -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 = { ), largeHeader: ({ children, ...rest }) => ( - + + {children} + + ), + mediumHeader: ({ children, ...rest }) => ( + + {children} + + ), + subHeader: ({ children, ...rest }) => ( + {children} ), @@ -113,6 +124,11 @@ export const TYPE = { {children} ), + green: ({ children, ...rest }) => ( + + {children} + + ), gray: ({ children, ...rest }) => ( {children}