Commit 677537ca authored by Ian Lapham's avatar Ian Lapham Committed by Noah Zinsmeister

Updates to Token Modal (#399)

parent be2012cd
...@@ -24,4 +24,6 @@ yarn-debug.log* ...@@ -24,4 +24,6 @@ yarn-debug.log*
yarn-error.log* yarn-error.log*
notes.txt notes.txt
.idea/ .idea/
\ No newline at end of file
.vscode/
\ No newline at end of file
...@@ -18,7 +18,8 @@ ...@@ -18,7 +18,8 @@
"unlock": "Unlock", "unlock": "Unlock",
"pending": "Pending", "pending": "Pending",
"selectToken": "Select a token", "selectToken": "Select a token",
"searchOrPaste": "Search Token or Paste Address", "searchOrPaste": "Search Token Name, Symbol, or Address",
"searchOrPasteMobile": "Name, Symbol, or Address",
"noExchange": "No Exchange Found", "noExchange": "No Exchange Found",
"exchangeRate": "Exchange Rate", "exchangeRate": "Exchange Rate",
"unknownError": "Oops! An unknown error occurred. Please refresh the page, or visit from another browser or device.", "unknownError": "Oops! An unknown error occurred. Please refresh the page, or visit from another browser or device.",
...@@ -36,6 +37,7 @@ ...@@ -36,6 +37,7 @@
"youWillReceive": "You will receive at least", "youWillReceive": "You will receive at least",
"youAreBuying": "You are buying", "youAreBuying": "You are buying",
"itWillCost": "It will cost at most", "itWillCost": "It will cost at most",
"forAtMost": "for at most",
"insufficientBalance": "Insufficient Balance", "insufficientBalance": "Insufficient Balance",
"inputNotValid": "Not a valid input value", "inputNotValid": "Not a valid input value",
"differentToken": "Must be different token.", "differentToken": "Must be different token.",
......
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 9.27455 20.9097 6.80375 19.1414 5" stroke="#AEAEAE" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
\ No newline at end of file
This diff is collapsed.
import React, { useState, useReducer, useEffect } from 'react' import React, { useState, useReducer, useEffect } from 'react'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useWeb3Context } from 'web3-react' import { useWeb3Context } from 'web3-react'
import { ethers } from 'ethers' import { ethers } from 'ethers'
import styled from 'styled-components' import styled from 'styled-components'
...@@ -16,6 +18,7 @@ import { useExchangeContract } from '../../hooks' ...@@ -16,6 +18,7 @@ import { useExchangeContract } from '../../hooks'
import { useTokenDetails } from '../../contexts/Tokens' import { useTokenDetails } from '../../contexts/Tokens'
import { useTransactionAdder } from '../../contexts/Transactions' import { useTransactionAdder } from '../../contexts/Transactions'
import { useAddressBalance, useExchangeReserves } from '../../contexts/Balances' import { useAddressBalance, useExchangeReserves } from '../../contexts/Balances'
import { useFetchAllBalances } from '../../contexts/AllBalances'
import { useAddressAllowance } from '../../contexts/Allowances' import { useAddressAllowance } from '../../contexts/Allowances'
const INPUT = 0 const INPUT = 0
...@@ -251,6 +254,7 @@ export default function ExchangePage({ initialCurrency, sending }) { ...@@ -251,6 +254,7 @@ export default function ExchangePage({ initialCurrency, sending }) {
// core swap state // core swap state
const [swapState, dispatchSwapState] = useReducer(swapStateReducer, initialCurrency, getInitialSwapState) const [swapState, dispatchSwapState] = useReducer(swapStateReducer, initialCurrency, getInitialSwapState)
const { independentValue, dependentValue, independentField, inputCurrency, outputCurrency } = swapState const { independentValue, dependentValue, independentField, inputCurrency, outputCurrency } = swapState
const [recipient, setRecipient] = useState({ address: '', name: '' }) const [recipient, setRecipient] = useState({ address: '', name: '' })
...@@ -583,10 +587,13 @@ export default function ExchangePage({ initialCurrency, sending }) { ...@@ -583,10 +587,13 @@ export default function ExchangePage({ initialCurrency, sending }) {
const [customSlippageError, setcustomSlippageError] = useState('') const [customSlippageError, setcustomSlippageError] = useState('')
const allBalances = useFetchAllBalances()
return ( return (
<> <>
<CurrencyInputPanel <CurrencyInputPanel
title={t('input')} title={t('input')}
allBalances={allBalances}
description={inputValueFormatted && independentField === OUTPUT ? estimatedText : ''} description={inputValueFormatted && independentField === OUTPUT ? estimatedText : ''}
extraText={inputBalanceFormatted && formatBalance(inputBalanceFormatted)} extraText={inputBalanceFormatted && formatBalance(inputBalanceFormatted)}
extraTextClickHander={() => { extraTextClickHander={() => {
...@@ -626,6 +633,7 @@ export default function ExchangePage({ initialCurrency, sending }) { ...@@ -626,6 +633,7 @@ export default function ExchangePage({ initialCurrency, sending }) {
</OversizedPanel> </OversizedPanel>
<CurrencyInputPanel <CurrencyInputPanel
title={t('output')} title={t('output')}
allBalances={allBalances}
description={outputValueFormatted && independentField === INPUT ? estimatedText : ''} description={outputValueFormatted && independentField === INPUT ? estimatedText : ''}
extraText={outputBalanceFormatted && formatBalance(outputBalanceFormatted)} extraText={outputBalanceFormatted && formatBalance(outputBalanceFormatted)}
onCurrencySelected={outputCurrency => { onCurrencySelected={outputCurrency => {
......
...@@ -28,7 +28,7 @@ const StyledDialogContent = styled(FilteredDialogContent)` ...@@ -28,7 +28,7 @@ const StyledDialogContent = styled(FilteredDialogContent)`
width: 50vw; width: 50vw;
max-width: 650px; max-width: 650px;
${({ theme }) => theme.mediaWidth.upToMedium`width: 65vw;`} ${({ theme }) => theme.mediaWidth.upToMedium`width: 65vw;`}
${({ theme }) => theme.mediaWidth.upToSmall`width: 80vw;`} ${({ theme }) => theme.mediaWidth.upToSmall`width: 85vw;`}
max-height: 50vh; max-height: 50vh;
${({ minHeight }) => ${({ minHeight }) =>
minHeight && minHeight &&
...@@ -39,7 +39,7 @@ const StyledDialogContent = styled(FilteredDialogContent)` ...@@ -39,7 +39,7 @@ const StyledDialogContent = styled(FilteredDialogContent)`
${({ theme }) => theme.mediaWidth.upToSmall`max-height: 80vh;`} ${({ theme }) => theme.mediaWidth.upToSmall`max-height: 80vh;`}
display: flex; display: flex;
overflow: hidden; overflow: hidden;
border-radius: 1.5rem; border-radius: 10px;
} }
` `
......
...@@ -10,12 +10,13 @@ const BAD_IMAGES = {} ...@@ -10,12 +10,13 @@ const BAD_IMAGES = {}
const Image = styled.img` const Image = styled.img`
width: ${({ size }) => size}; width: ${({ size }) => size};
height: ${({ size }) => size}; height: ${({ size }) => size};
background-color: white;
border-radius: 1rem; border-radius: 1rem;
` `
const Emoji = styled.span` const Emoji = styled.span`
width: ${({ size }) => size}; width: ${({ size }) => size};
font-size: ${({ size }) => size}; height: ${({ size }) => size};
` `
const StyledEthereumLogo = styled(EthereumLogo)` const StyledEthereumLogo = styled(EthereumLogo)`
...@@ -33,7 +34,7 @@ export default function TokenLogo({ address, size = '1rem', ...rest }) { ...@@ -33,7 +34,7 @@ export default function TokenLogo({ address, size = '1rem', ...rest }) {
path = TOKEN_ICON_API(address.toLowerCase()) path = TOKEN_ICON_API(address.toLowerCase())
} else { } else {
return ( return (
<Emoji {...rest}> <Emoji {...rest} size={size}>
<span role="img" aria-label="Thinking"> <span role="img" aria-label="Thinking">
🤔 🤔
</span> </span>
......
...@@ -580,7 +580,6 @@ export default function TransactionDetails(props) { ...@@ -580,7 +580,6 @@ export default function TransactionDetails(props) {
)} ${props.inputSymbol}` )} ${props.inputSymbol}`
)} )}
</ValueWrapper> </ValueWrapper>
.
</div> </div>
<LastSummaryText> <LastSummaryText>
{b(props.recipientAddress)} {t('willReceive')}{' '} {b(props.recipientAddress)} {t('willReceive')}{' '}
...@@ -595,7 +594,7 @@ export default function TransactionDetails(props) { ...@@ -595,7 +594,7 @@ export default function TransactionDetails(props) {
</ValueWrapper>{' '} </ValueWrapper>{' '}
</LastSummaryText> </LastSummaryText>
<LastSummaryText> <LastSummaryText>
{t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>. {t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>
</LastSummaryText> </LastSummaryText>
</TransactionInfo> </TransactionInfo>
) : ( ) : (
...@@ -623,7 +622,7 @@ export default function TransactionDetails(props) { ...@@ -623,7 +622,7 @@ export default function TransactionDetails(props) {
</ValueWrapper> </ValueWrapper>
</div> </div>
<LastSummaryText> <LastSummaryText>
{t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>. {t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>
</LastSummaryText> </LastSummaryText>
</TransactionInfo> </TransactionInfo>
) )
...@@ -641,10 +640,7 @@ export default function TransactionDetails(props) { ...@@ -641,10 +640,7 @@ export default function TransactionDetails(props) {
)} ${props.outputSymbol}` )} ${props.outputSymbol}`
)} )}
</ValueWrapper>{' '} </ValueWrapper>{' '}
{t('to')} {b(props.recipientAddress)} {t('to')} {b(props.recipientAddress)} {t('forAtMost')}{' '}
</div>
<LastSummaryText>
{t('itWillCost')}{' '}
<ValueWrapper> <ValueWrapper>
{b( {b(
`${amountFormatter( `${amountFormatter(
...@@ -654,39 +650,35 @@ export default function TransactionDetails(props) { ...@@ -654,39 +650,35 @@ export default function TransactionDetails(props) {
)} ${props.inputSymbol}` )} ${props.inputSymbol}`
)} )}
</ValueWrapper>{' '} </ValueWrapper>{' '}
</LastSummaryText> </div>
<LastSummaryText> <LastSummaryText>
{t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>. {t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>
</LastSummaryText> </LastSummaryText>
</TransactionInfo> </TransactionInfo>
) : ( ) : (
<TransactionInfo> <TransactionInfo>
<div> {t('youAreBuying')}{' '}
{t('youAreBuying')}{' '} <ValueWrapper>
<ValueWrapper> {b(
{b( `${amountFormatter(
`${amountFormatter( props.independentValueParsed,
props.independentValueParsed, props.independentDecimals,
props.independentDecimals, Math.min(4, props.independentDecimals)
Math.min(4, props.independentDecimals) )} ${props.outputSymbol}`
)} ${props.outputSymbol}` )}
)} </ValueWrapper>{' '}
</ValueWrapper> {t('forAtMost')}{' '}
</div> <ValueWrapper>
<LastSummaryText> {b(
{t('itWillCost')}{' '} `${amountFormatter(
<ValueWrapper> props.dependentValueMaximum,
{b( props.dependentDecimals,
`${amountFormatter( Math.min(4, props.dependentDecimals)
props.dependentValueMaximum, )} ${props.inputSymbol}`
props.dependentDecimals, )}
Math.min(4, props.dependentDecimals) </ValueWrapper>{' '}
)} ${props.inputSymbol}`
)}
</ValueWrapper>{' '}
</LastSummaryText>
<LastSummaryText> <LastSummaryText>
{t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>. {t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>
</LastSummaryText> </LastSummaryText>
</TransactionInfo> </TransactionInfo>
) )
......
...@@ -59,9 +59,13 @@ const Web3StatusConnected = styled(Web3StatusGeneric)` ...@@ -59,9 +59,13 @@ const Web3StatusConnected = styled(Web3StatusGeneric)`
color: ${({ pending, theme }) => (pending ? theme.royalBlue : theme.doveGray)}; color: ${({ pending, theme }) => (pending ? theme.royalBlue : theme.doveGray)};
font-weight: 400; font-weight: 400;
:hover { :hover {
> P {
color: ${({ theme }) => theme.uniswapPink};
}
background-color: ${({ pending, theme }) => background-color: ${({ pending, theme }) =>
pending ? transparentize(0.9, theme.royalBlue) : transparentize(0.9, theme.mercuryGray)}; pending ? transparentize(0.9, theme.royalBlue) : transparentize(0.9, theme.mercuryGray)};
}
:focus { :focus {
border: 1px solid border: 1px solid
${({ pending, theme }) => (pending ? darken(0.1, theme.royalBlue) : darken(0.1, theme.mercuryGray))}; ${({ pending, theme }) => (pending ? darken(0.1, theme.royalBlue) : darken(0.1, theme.mercuryGray))};
......
import React, { createContext, useContext, useReducer, useMemo, useCallback } from 'react'
import { ethers } from 'ethers'
import { getTokenReserves, getMarketDetails, BigNumber } from '@uniswap/sdk'
import { useWeb3Context } from 'web3-react'
import { safeAccess, isAddress, getEtherBalance, getTokenBalance } from '../utils'
import { useAllTokenDetails } from './Tokens'
const ZERO = ethers.utils.bigNumberify(0)
const ONE = new BigNumber(1)
const UPDATE = 'UPDATE'
const AllBalancesContext = createContext()
function useAllBalancesContext() {
return useContext(AllBalancesContext)
}
function reducer(state, { type, payload }) {
switch (type) {
case UPDATE: {
const { allBalanceData, networkId, address } = payload
return {
...state,
[networkId]: {
...(safeAccess(state, [networkId]) || {}),
[address]: {
...(safeAccess(state, [networkId, address]) || {}),
allBalanceData
}
}
}
}
default: {
throw Error(`Unexpected action type in AllBalancesContext reducer: '${type}'.`)
}
}
}
export default function Provider({ children }) {
const [state, dispatch] = useReducer(reducer, {})
const update = useCallback((allBalanceData, networkId, address) => {
dispatch({ type: UPDATE, payload: { allBalanceData, networkId, address } })
}, [])
return (
<AllBalancesContext.Provider value={useMemo(() => [state, { update }], [state, update])}>
{children}
</AllBalancesContext.Provider>
)
}
export function useFetchAllBalances() {
const { account, networkId, library } = useWeb3Context()
const allTokens = useAllTokenDetails()
const [state, { update }] = useAllBalancesContext()
const { allBalanceData } = safeAccess(state, [networkId, account]) || {}
const getData = async () => {
if (!!library && !!account) {
const newBalances = {}
await Promise.all(
Object.keys(allTokens).map(async k => {
let balance = null
let ethRate = null
if (isAddress(k) || k === 'ETH') {
if (k === 'ETH') {
balance = await getEtherBalance(account, library).catch(() => null)
ethRate = ONE
} else {
balance = await getTokenBalance(k, account, library).catch(() => null)
// only get values for tokens with positive balances
if (!!balance && balance.gt(ZERO)) {
const tokenReserves = await getTokenReserves(k, library).catch(() => null)
if (!!tokenReserves) {
const marketDetails = getMarketDetails(tokenReserves)
if (marketDetails.marketRate && marketDetails.marketRate.rate) {
ethRate = marketDetails.marketRate.rate
}
}
}
}
return (newBalances[k] = { balance, ethRate })
}
})
)
update(newBalances, networkId, account)
}
}
useMemo(getData, [account, state])
return allBalanceData
}
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react' import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
import { useWeb3Context } from 'web3-react' import { useWeb3Context } from 'web3-react'
import { safeAccess } from '../utils' import { safeAccess } from '../utils'
import { getUSDPrice } from '../utils/price'
const BLOCK_NUMBERS = 'BLOCK_NUMBERS' const BLOCK_NUMBER = 'BLOCK_NUMBER'
const USD_PRICE = 'USD_PRICE'
const UPDATE_BLOCK_NUMBER = 'UPDATE_BLOCK_NUMBER' const UPDATE_BLOCK_NUMBER = 'UPDATE_BLOCK_NUMBER'
const UPDATE_USD_PRICE = 'UPDATE_USD_PRICE'
const ApplicationContext = createContext() const ApplicationContext = createContext()
...@@ -18,12 +22,22 @@ function reducer(state, { type, payload }) { ...@@ -18,12 +22,22 @@ function reducer(state, { type, payload }) {
const { networkId, blockNumber } = payload const { networkId, blockNumber } = payload
return { return {
...state, ...state,
[BLOCK_NUMBERS]: { [BLOCK_NUMBER]: {
...(safeAccess(state, [BLOCK_NUMBERS]) || {}), ...(safeAccess(state, [BLOCK_NUMBER]) || {}),
[networkId]: blockNumber [networkId]: blockNumber
} }
} }
} }
case UPDATE_USD_PRICE: {
const { networkId, USDPrice } = payload
return {
...state,
[USD_PRICE]: {
...(safeAccess(state, [USD_PRICE]) || {}),
[networkId]: USDPrice
}
}
}
default: { default: {
throw Error(`Unexpected action type in ApplicationContext reducer: '${type}'.`) throw Error(`Unexpected action type in ApplicationContext reducer: '${type}'.`)
} }
...@@ -32,15 +46,22 @@ function reducer(state, { type, payload }) { ...@@ -32,15 +46,22 @@ function reducer(state, { type, payload }) {
export default function Provider({ children }) { export default function Provider({ children }) {
const [state, dispatch] = useReducer(reducer, { const [state, dispatch] = useReducer(reducer, {
[BLOCK_NUMBERS]: {} [BLOCK_NUMBER]: {},
[USD_PRICE]: {}
}) })
const updateBlockNumber = useCallback((networkId, blockNumber) => { const updateBlockNumber = useCallback((networkId, blockNumber) => {
dispatch({ type: UPDATE_BLOCK_NUMBER, payload: { networkId, blockNumber } }) dispatch({ type: UPDATE_BLOCK_NUMBER, payload: { networkId, blockNumber } })
}, []) }, [])
const updateUSDPrice = useCallback((networkId, USDPrice) => {
dispatch({ type: UPDATE_USD_PRICE, payload: { networkId, USDPrice } })
}, [])
return ( return (
<ApplicationContext.Provider value={useMemo(() => [state, { updateBlockNumber }], [state, updateBlockNumber])}> <ApplicationContext.Provider
value={useMemo(() => [state, { updateBlockNumber, updateUSDPrice }], [state, updateBlockNumber, updateUSDPrice])}
>
{children} {children}
</ApplicationContext.Provider> </ApplicationContext.Provider>
) )
...@@ -49,7 +70,24 @@ export default function Provider({ children }) { ...@@ -49,7 +70,24 @@ export default function Provider({ children }) {
export function Updater() { export function Updater() {
const { networkId, library } = useWeb3Context() const { networkId, library } = useWeb3Context()
const [, { updateBlockNumber }] = useApplicationContext() const globalBlockNumber = useBlockNumber()
const [, { updateBlockNumber, updateUSDPrice }] = useApplicationContext()
useEffect(() => {
let stale = false
getUSDPrice(library)
.then(([price]) => {
if (!stale) {
updateUSDPrice(networkId, price)
}
})
.catch(() => {
if (!stale) {
updateUSDPrice(networkId, null)
}
})
}, [globalBlockNumber, library, networkId, updateUSDPrice])
useEffect(() => { useEffect(() => {
if ((networkId || networkId === 0) && library) { if ((networkId || networkId === 0) && library) {
...@@ -88,5 +126,13 @@ export function useBlockNumber() { ...@@ -88,5 +126,13 @@ export function useBlockNumber() {
const [state] = useApplicationContext() const [state] = useApplicationContext()
return safeAccess(state, [BLOCK_NUMBERS, networkId]) return safeAccess(state, [BLOCK_NUMBER, networkId])
}
export function useUSDPrice() {
const { networkId } = useWeb3Context()
const [state] = useApplicationContext()
return safeAccess(state, [USD_PRICE, networkId])
} }
...@@ -79,7 +79,6 @@ export function useAddressBalance(address, tokenAddress) { ...@@ -79,7 +79,6 @@ export function useAddressBalance(address, tokenAddress) {
update(networkId, address, tokenAddress, null, globalBlockNumber) update(networkId, address, tokenAddress, null, globalBlockNumber)
} }
}) })
return () => { return () => {
stale = true stale = true
} }
......
...@@ -8,6 +8,7 @@ const LAST_SAVED = 'LAST_SAVED' ...@@ -8,6 +8,7 @@ const LAST_SAVED = 'LAST_SAVED'
const BETA_MESSAGE_DISMISSED = 'BETA_MESSAGE_DISMISSED' const BETA_MESSAGE_DISMISSED = 'BETA_MESSAGE_DISMISSED'
const DARK_MODE = 'DARK_MODE' const DARK_MODE = 'DARK_MODE'
const UPDATABLE_KEYS = [BETA_MESSAGE_DISMISSED, DARK_MODE] const UPDATABLE_KEYS = [BETA_MESSAGE_DISMISSED, DARK_MODE]
const UPDATE_KEY = 'UPDATE_KEY' const UPDATE_KEY = 'UPDATE_KEY'
......
...@@ -466,7 +466,6 @@ export function useTokenDetails(tokenAddress) { ...@@ -466,7 +466,6 @@ export function useTokenDetails(tokenAddress) {
} }
} }
) )
return () => { return () => {
stale = true stale = true
} }
......
...@@ -10,6 +10,7 @@ import TransactionContextProvider, { Updater as TransactionContextUpdater } from ...@@ -10,6 +10,7 @@ import TransactionContextProvider, { Updater as TransactionContextUpdater } from
import TokensContextProvider from './contexts/Tokens' import TokensContextProvider from './contexts/Tokens'
import BalancesContextProvider from './contexts/Balances' import BalancesContextProvider from './contexts/Balances'
import AllowancesContextProvider from './contexts/Allowances' import AllowancesContextProvider from './contexts/Allowances'
import AllBalancesContextProvider from './contexts/AllBalances'
import App from './pages/App' import App from './pages/App'
import InjectedConnector from './InjectedConnector' import InjectedConnector from './InjectedConnector'
...@@ -35,7 +36,9 @@ function ContextProviders({ children }) { ...@@ -35,7 +36,9 @@ function ContextProviders({ children }) {
<TransactionContextProvider> <TransactionContextProvider>
<TokensContextProvider> <TokensContextProvider>
<BalancesContextProvider> <BalancesContextProvider>
<AllowancesContextProvider>{children}</AllowancesContextProvider> <AllBalancesContextProvider>
<AllowancesContextProvider>{children}</AllowancesContextProvider>
</AllBalancesContextProvider>
</BalancesContextProvider> </BalancesContextProvider>
</TokensContextProvider> </TokensContextProvider>
</TransactionContextProvider> </TransactionContextProvider>
......
...@@ -15,6 +15,7 @@ import { useExchangeContract } from '../../hooks' ...@@ -15,6 +15,7 @@ import { useExchangeContract } from '../../hooks'
import { amountFormatter, calculateGasMargin } from '../../utils' import { amountFormatter, calculateGasMargin } from '../../utils'
import { useTransactionAdder } from '../../contexts/Transactions' import { useTransactionAdder } from '../../contexts/Transactions'
import { useTokenDetails } from '../../contexts/Tokens' import { useTokenDetails } from '../../contexts/Tokens'
import { useFetchAllBalances } from '../../contexts/AllBalances'
import { useAddressBalance, useExchangeReserves } from '../../contexts/Balances' import { useAddressBalance, useExchangeReserves } from '../../contexts/Balances'
import { useAddressAllowance } from '../../contexts/Allowances' import { useAddressAllowance } from '../../contexts/Allowances'
...@@ -534,6 +535,8 @@ export default function AddLiquidity() { ...@@ -534,6 +535,8 @@ export default function AddLiquidity() {
const isActive = active && account const isActive = active && account
const isValid = (inputError === null || outputError === null) && !showUnlock const isValid = (inputError === null || outputError === null) && !showUnlock
const allBalances = useFetchAllBalances()
return ( return (
<> <>
{isNewExchange ? ( {isNewExchange ? (
...@@ -550,6 +553,7 @@ export default function AddLiquidity() { ...@@ -550,6 +553,7 @@ export default function AddLiquidity() {
<CurrencyInputPanel <CurrencyInputPanel
title={t('deposit')} title={t('deposit')}
allBalances={allBalances}
extraText={inputBalance && formatBalance(amountFormatter(inputBalance, 18, 4))} extraText={inputBalance && formatBalance(amountFormatter(inputBalance, 18, 4))}
onValueChange={inputValue => { onValueChange={inputValue => {
dispatchAddLiquidityState({ type: 'UPDATE_VALUE', payload: { value: inputValue, field: INPUT } }) dispatchAddLiquidityState({ type: 'UPDATE_VALUE', payload: { value: inputValue, field: INPUT } })
...@@ -566,6 +570,7 @@ export default function AddLiquidity() { ...@@ -566,6 +570,7 @@ export default function AddLiquidity() {
</OversizedPanel> </OversizedPanel>
<CurrencyInputPanel <CurrencyInputPanel
title={t('deposit')} title={t('deposit')}
allBalances={allBalances}
description={isNewExchange ? '' : outputValue ? `(${t('estimated')})` : ''} description={isNewExchange ? '' : outputValue ? `(${t('estimated')})` : ''}
extraText={outputBalance && formatBalance(amountFormatter(outputBalance, decimals, Math.min(decimals, 4)))} extraText={outputBalance && formatBalance(amountFormatter(outputBalance, decimals, Math.min(decimals, 4)))}
selectedTokenAddress={outputCurrency} selectedTokenAddress={outputCurrency}
......
...@@ -15,6 +15,7 @@ import { useExchangeContract } from '../../hooks' ...@@ -15,6 +15,7 @@ import { useExchangeContract } from '../../hooks'
import { useTransactionAdder } from '../../contexts/Transactions' import { useTransactionAdder } from '../../contexts/Transactions'
import { useTokenDetails } from '../../contexts/Tokens' import { useTokenDetails } from '../../contexts/Tokens'
import { useAddressBalance } from '../../contexts/Balances' import { useAddressBalance } from '../../contexts/Balances'
import { useFetchAllBalances } from '../../contexts/AllBalances'
import { calculateGasMargin, amountFormatter } from '../../utils' import { calculateGasMargin, amountFormatter } from '../../utils'
// denominated in bips // denominated in bips
...@@ -328,10 +329,13 @@ export default function RemoveLiquidity() { ...@@ -328,10 +329,13 @@ export default function RemoveLiquidity() {
const marketRate = getMarketRate(exchangeETHBalance, exchangeTokenBalance, decimals) const marketRate = getMarketRate(exchangeETHBalance, exchangeTokenBalance, decimals)
const allBalances = useFetchAllBalances()
return ( return (
<> <>
<CurrencyInputPanel <CurrencyInputPanel
title={t('poolTokens')} title={t('poolTokens')}
allBalances={allBalances}
extraText={poolTokenBalance && formatBalance(amountFormatter(poolTokenBalance, 18, 4))} extraText={poolTokenBalance && formatBalance(amountFormatter(poolTokenBalance, 18, 4))}
extraTextClickHander={() => { extraTextClickHander={() => {
if (poolTokenBalance) { if (poolTokenBalance) {
...@@ -354,6 +358,7 @@ export default function RemoveLiquidity() { ...@@ -354,6 +358,7 @@ export default function RemoveLiquidity() {
</OversizedPanel> </OversizedPanel>
<CurrencyInputPanel <CurrencyInputPanel
title={t('output')} title={t('output')}
allBalances={allBalances}
description={!!(ethWithdrawn && tokenWithdrawn) ? `(${t('estimated')})` : ''} description={!!(ethWithdrawn && tokenWithdrawn) ? `(${t('estimated')})` : ''}
key="remove-liquidity-input" key="remove-liquidity-input"
renderInput={() => renderInput={() =>
......
...@@ -69,7 +69,7 @@ export const BorderlessInput = styled.input` ...@@ -69,7 +69,7 @@ export const BorderlessInput = styled.input`
} }
::placeholder { ::placeholder {
color: ${({ theme }) => theme.placeholderGray}; color: ${({ theme }) => theme.chaliceGray};
} }
` `
......
...@@ -51,12 +51,14 @@ const theme = darkMode => ({ ...@@ -51,12 +51,14 @@ const theme = darkMode => ({
doveGray: darkMode ? '#C4C4C4' : '#737373', doveGray: darkMode ? '#C4C4C4' : '#737373',
mineshaftGray: darkMode ? '#E1E1E1' : '#2B2B2B', mineshaftGray: darkMode ? '#E1E1E1' : '#2B2B2B',
buttonOutlineGrey: darkMode ? '#FAFAFA' : '#F2F2F2', buttonOutlineGrey: darkMode ? '#FAFAFA' : '#F2F2F2',
tokenRowHover: darkMode ? '#404040' : '#F2F2F2',
//blacks //blacks
charcoalBlack: darkMode ? '#F2F2F2' : '#404040', charcoalBlack: darkMode ? '#F2F2F2' : '#404040',
// blues // blues
zumthorBlue: darkMode ? '#212529' : '#EBF4FF', zumthorBlue: darkMode ? '#212529' : '#EBF4FF',
malibuBlue: darkMode ? '#E67AEF' : '#5CA2FF', malibuBlue: darkMode ? '#E67AEF' : '#5CA2FF',
royalBlue: darkMode ? '#DC6BE5' : '#2F80ED', royalBlue: darkMode ? '#DC6BE5' : '#2F80ED',
loadingBlue: darkMode ? '#e4f0ff' : '#e4f0ff',
// purples // purples
wisteriaPurple: '#DC6BE5', wisteriaPurple: '#DC6BE5',
// reds // reds
...@@ -69,6 +71,9 @@ const theme = darkMode => ({ ...@@ -69,6 +71,9 @@ const theme = darkMode => ({
uniswapPink: '#DC6BE5', uniswapPink: '#DC6BE5',
connectedGreen: '#27AE60', connectedGreen: '#27AE60',
//specific
textHover: darkMode ? theme.uniswapPink : theme.doveGray,
// media queries // media queries
mediaWidth: mediaWidthTemplates, mediaWidth: mediaWidthTemplates,
// css snippets // css snippets
......
...@@ -5,6 +5,7 @@ import EXCHANGE_ABI from '../constants/abis/exchange' ...@@ -5,6 +5,7 @@ import EXCHANGE_ABI from '../constants/abis/exchange'
import ERC20_ABI from '../constants/abis/erc20' import ERC20_ABI from '../constants/abis/erc20'
import ERC20_BYTES32_ABI from '../constants/abis/erc20_bytes32' import ERC20_BYTES32_ABI from '../constants/abis/erc20_bytes32'
import { FACTORY_ADDRESSES } from '../constants' import { FACTORY_ADDRESSES } from '../constants'
import { formatFixed } from '@uniswap/sdk'
import UncheckedJsonRpcSigner from './signer' import UncheckedJsonRpcSigner from './signer'
...@@ -178,10 +179,27 @@ export async function getEtherBalance(address, library) { ...@@ -178,10 +179,27 @@ export async function getEtherBalance(address, library) {
if (!isAddress(address)) { if (!isAddress(address)) {
throw Error(`Invalid 'address' parameter '${address}'`) throw Error(`Invalid 'address' parameter '${address}'`)
} }
return library.getBalance(address) return library.getBalance(address)
} }
export function formatEthBalance(balance) {
return amountFormatter(balance, 18, 6)
}
export function formatTokenBalance(balance, decimal) {
return !!(balance && Number.isInteger(decimal)) ? amountFormatter(balance, decimal, Math.min(4, decimal)) : 0
}
export function formatToUsd(price) {
const format = { decimalSeparator: '.', groupSeparator: ',', groupSize: 3 }
const usdPrice = formatFixed(price, {
decimalPlaces: 2,
dropTrailingZeros: false,
format
})
return usdPrice
}
// get the token balance of an address // get the token balance of an address
export async function getTokenBalance(tokenAddress, address, library) { export async function getTokenBalance(tokenAddress, address, library) {
if (!isAddress(tokenAddress) || !isAddress(address)) { if (!isAddress(tokenAddress) || !isAddress(address)) {
......
import { BigNumber } from '@uniswap/sdk'
// returns a deep copied + sorted list of values, as well as a sortmap
export function sortBigNumbers(values) {
const valueMap = values.map((value, i) => ({ value, i }))
valueMap.sort((a, b) => {
if (a.value.isGreaterThan(b.value)) {
return 1
} else if (a.value.isLessThan(b.value)) {
return -1
} else {
return 0
}
})
return [
valueMap.map(element => values[element.i]),
values.map((_, i) => valueMap.findIndex(element => element.i === i))
]
}
export function getMedian(values) {
const [sortedValues, sortMap] = sortBigNumbers(values)
if (values.length % 2 === 0) {
const middle = values.length / 2
const indices = [middle - 1, middle]
return [
sortedValues[middle - 1].plus(sortedValues[middle]).dividedBy(2),
sortMap.map(element => (indices.includes(element) ? new BigNumber(0.5) : new BigNumber(0)))
]
} else {
const middle = Math.floor(values.length / 2)
return [sortedValues[middle], sortMap.map(element => (element === middle ? new BigNumber(1) : new BigNumber(0)))]
}
}
export function getMean(values, _weights) {
const weights = _weights ? _weights : values.map(() => new BigNumber(1))
const weightedValues = values.map((value, i) => value.multipliedBy(weights[i]))
const numerator = weightedValues.reduce(
(accumulator, currentValue) => accumulator.plus(currentValue),
new BigNumber(0)
)
const denominator = weights.reduce((accumulator, currentValue) => accumulator.plus(currentValue), new BigNumber(0))
return [numerator.dividedBy(denominator), weights.map(weight => weight.dividedBy(denominator))]
}
import { getTokenReserves, getMarketDetails } from '@uniswap/sdk'
import { getMedian, getMean } from './math'
const DAI = 'DAI'
const USDC = 'USDC'
const TUSD = 'TUSD'
const USD_STABLECOINS = [DAI, USDC, TUSD]
const USD_STABLECOIN_ADDRESSES = [
'0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359',
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
'0x8dd5fbCe2F6a956C3022bA3663759011Dd51e73E'
]
function forEachStablecoin(runner) {
return USD_STABLECOINS.map((stablecoin, index) => runner(index, stablecoin))
}
export async function getUSDPrice(library) {
return Promise.all(forEachStablecoin(i => getTokenReserves(USD_STABLECOIN_ADDRESSES[i], library))).then(reserves => {
const ethReserves = forEachStablecoin(i => reserves[i].ethReserve.amount)
const marketDetails = forEachStablecoin(i => getMarketDetails(reserves[i], undefined))
const ethPrices = forEachStablecoin(i => marketDetails[i].marketRate.rateInverted)
const [median, medianWeights] = getMedian(ethPrices)
const [mean, meanWeights] = getMean(ethPrices)
const [weightedMean, weightedMeanWeights] = getMean(ethPrices, ethReserves)
const ethPrice = getMean([median, mean, weightedMean])[0]
const _stablecoinWeights = [
getMean([medianWeights[0], meanWeights[0], weightedMeanWeights[0]])[0],
getMean([medianWeights[1], meanWeights[1], weightedMeanWeights[1]])[0],
getMean([medianWeights[2], meanWeights[2], weightedMeanWeights[2]])[0]
]
const stablecoinWeights = forEachStablecoin((i, stablecoin) => ({
[stablecoin]: _stablecoinWeights[i]
})).reduce((accumulator, currentValue) => ({ ...accumulator, ...currentValue }), {})
return [ethPrice, stablecoinWeights]
})
}
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment