Merge branch 'beta' into production

This commit is contained in:
Noah Zinsmeister 2019-12-12 12:01:21 -05:00
commit 31499ee2b1
No known key found for this signature in database
GPG Key ID: 83022DD49188C9F2
12 changed files with 326 additions and 239 deletions

@ -11,10 +11,18 @@ This an an open source interface for Uniswap - a protocol for decentralized exch
- Twitter: [@UniswapExchange](https://twitter.com/UniswapExchange) - Twitter: [@UniswapExchange](https://twitter.com/UniswapExchange)
- Reddit: [/r/Uniswap](https://www.reddit.com/r/UniSwap/) - Reddit: [/r/Uniswap](https://www.reddit.com/r/UniSwap/)
- Email: [contact@uniswap.io](mailto:contact@uniswap.io) - Email: [contact@uniswap.io](mailto:contact@uniswap.io)
- Slack: [uni-swap.slack.com/](https://join.slack.com/t/uni-swap/shared_invite/enQtNDYwMjg1ODc5ODA4LWEyYmU0OGU1ZGQ3NjE4YzhmNzcxMDAyM2ExNzNkZjZjZjcxYTkwNzU0MGE3M2JkNzMxOTA2MzE2ZWM0YWQwNjU) - Discord: [Uniswap](https://discord.gg/Y7TF6QA)
- Whitepaper: [Link](https://hackmd.io/C-DvwDSfSxuh-Gd4WKE_ig) - Whitepaper: [Link](https://hackmd.io/C-DvwDSfSxuh-Gd4WKE_ig)
## To Start Development ## Run Uniswap Locally
1. Download and unzip the `build.zip` file from the latest release in the [Releases tab](https://github.com/Uniswap/uniswap-frontend/releases/latest).
2. Serve the `build/` folder locally, and access the application via a browser.
For more information on running a local server see [https://developer.mozilla.org/en-US/docs/Learn/Common_questions/set_up_a_local_testing_server](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/set_up_a_local_testing_server). This simple approach has one downside: refreshing the page will give a `404` because of how React handles client-side routing. To fix this issue, consider running `serve -s` courtesy of the [serve](https://github.com/zeit/serve) package.
## Development Uniswap Locally
### Install Dependencies ### Install Dependencies

@ -24,7 +24,7 @@ import { ReactComponent as Close } from '../../assets/images/x.svg'
import { transparentize } from 'polished' import { transparentize } from 'polished'
import { Spinner } from '../../theme' import { Spinner } from '../../theme'
import Circle from '../../assets/images/circle-grey.svg' import Circle from '../../assets/images/circle-grey.svg'
import { useUSDPrice } from '../../contexts/Application' import { useETHPriceInUSD, useAllBalances } from '../../contexts/Balances'
const GAS_MARGIN = ethers.utils.bigNumberify(1000) const GAS_MARGIN = ethers.utils.bigNumberify(1000)
@ -439,7 +439,7 @@ export default function CurrencyInputPanel({
) )
} }
function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect, allBalances }) { function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect }) {
const { t } = useTranslation() const { t } = useTranslation()
const [searchQuery, setSearchQuery] = useState('') const [searchQuery, setSearchQuery] = useState('')
@ -450,19 +450,24 @@ function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect, allBalances })
const { account } = useWeb3React() const { account } = useWeb3React()
// BigNumber.js instance // BigNumber.js instance
const ethPrice = useUSDPrice() const ethPrice = useETHPriceInUSD()
// all balances for both account and exchanges
let allBalances = useAllBalances()
const _usdAmounts = Object.keys(allTokens).map(k => { const _usdAmounts = Object.keys(allTokens).map(k => {
if ( if (ethPrice && allBalances[account] && allBalances[account][k]) {
ethPrice && let ethRate = 1 // default for ETH
allBalances && let exchangeDetails = allBalances[allTokens[k].exchangeAddress]
allBalances[k] && if (exchangeDetails && exchangeDetails[k] && exchangeDetails['ETH']) {
allBalances[k].ethRate && const tokenBalance = new BigNumber(exchangeDetails[k].value.toString())
!allBalances[k].ethRate.isNaN() && const ethBalance = new BigNumber(exchangeDetails['ETH'].value.toString())
allBalances[k].balance ethRate = ethBalance.div(tokenBalance)
) { }
const USDRate = ethPrice.times(allBalances[k].ethRate) const USDRate = ethPrice
const balanceBigNumber = new BigNumber(allBalances[k].balance.toString()) .times(ethRate)
.times(new BigNumber(10).pow(allTokens[k].decimals).div(new BigNumber(10).pow(18)))
const balanceBigNumber = new BigNumber(allBalances[account][k].value.toString())
const usdBalance = balanceBigNumber.times(USDRate).div(new BigNumber(10).pow(allTokens[k].decimals)) const usdBalance = balanceBigNumber.times(USDRate).div(new BigNumber(10).pow(allTokens[k].decimals))
return usdBalance return usdBalance
} else { } else {
@ -506,11 +511,11 @@ function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect, allBalances })
let balance let balance
let usdBalance let usdBalance
// only update if we have data // only update if we have data
if (k === 'ETH' && allBalances && allBalances[k]) { if (k === 'ETH' && allBalances[account] && allBalances[account][k]) {
balance = formatEthBalance(allBalances[k].balance) balance = formatEthBalance(allBalances[account][k].value)
usdBalance = usdAmounts[k] usdBalance = usdAmounts[k]
} else if (allBalances && allBalances[k]) { } else if (allBalances[account] && allBalances[account][k]) {
balance = formatTokenBalance(allBalances[k].balance, allTokens[k].decimals) balance = formatTokenBalance(allBalances[account][k].value, allTokens[k].decimals)
usdBalance = usdAmounts[k] usdBalance = usdAmounts[k]
} }
return { return {
@ -521,7 +526,7 @@ function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect, allBalances })
usdBalance: usdBalance usdBalance: usdBalance
} }
}) })
}, [allBalances, allTokens, usdAmounts]) }, [allBalances, allTokens, usdAmounts, account])
const filteredTokenList = useMemo(() => { const filteredTokenList = useMemo(() => {
return tokenList.filter(tokenEntry => { return tokenList.filter(tokenEntry => {
@ -580,7 +585,13 @@ function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect, allBalances })
'-' '-'
)} )}
<TokenRowUsd> <TokenRowUsd>
{usdBalance ? (usdBalance.lt(0.01) ? '<$0.01' : '$' + formatToUsd(usdBalance)) : ''} {usdBalance
? usdBalance.isZero()
? ''
: usdBalance.lt(0.01)
? '<$0.01'
: '$' + formatToUsd(usdBalance)
: ''}
</TokenRowUsd> </TokenRowUsd>
</TokenRowRight> </TokenRowRight>
</TokenModalRow> </TokenModalRow>

@ -17,7 +17,6 @@ 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'
import { useWalletModalToggle } from '../../contexts/Application' import { useWalletModalToggle } from '../../contexts/Application'
@ -33,7 +32,7 @@ const ALLOWED_SLIPPAGE_DEFAULT = 100
const TOKEN_ALLOWED_SLIPPAGE_DEFAULT = 100 const TOKEN_ALLOWED_SLIPPAGE_DEFAULT = 100
// 15 minutes, denominated in seconds // 15 minutes, denominated in seconds
const DEADLINE_FROM_NOW = 60 * 15 const DEFAULT_DEADLINE_FROM_NOW = 60 * 15
// % above the calculated gas cost that we actually send, denominated in bips // % above the calculated gas cost that we actually send, denominated in bips
const GAS_MARGIN = ethers.utils.bigNumberify(1000) const GAS_MARGIN = ethers.utils.bigNumberify(1000)
@ -267,6 +266,8 @@ export default function ExchangePage({ initialCurrency, sending = false, params
return '' return ''
} }
const [deadlineFromNow, setDeadlineFromNow] = useState(DEFAULT_DEADLINE_FROM_NOW)
const [rawSlippage, setRawSlippage] = useState(() => initialSlippage()) const [rawSlippage, setRawSlippage] = useState(() => initialSlippage())
const [rawTokenSlippage, setRawTokenSlippage] = useState(() => initialSlippage(true)) const [rawTokenSlippage, setRawTokenSlippage] = useState(() => initialSlippage(true))
@ -554,8 +555,8 @@ export default function ExchangePage({ initialCurrency, sending = false, params
const highSlippageWarning = percentSlippage && percentSlippage.gte(ethers.utils.parseEther('.2')) // [20+% const highSlippageWarning = percentSlippage && percentSlippage.gte(ethers.utils.parseEther('.2')) // [20+%
const isValid = sending const isValid = sending
? exchangeRate && inputError === null && independentError === null && recipientError === null ? exchangeRate && inputError === null && independentError === null && recipientError === null && deadlineFromNow
: exchangeRate && inputError === null && independentError === null : exchangeRate && inputError === null && independentError === null && deadlineFromNow
const estimatedText = `(${t('estimated')})` const estimatedText = `(${t('estimated')})`
function formatBalance(value) { function formatBalance(value) {
@ -563,7 +564,7 @@ export default function ExchangePage({ initialCurrency, sending = false, params
} }
async function onSwap() { async function onSwap() {
const deadline = Math.ceil(Date.now() / 1000) + DEADLINE_FROM_NOW const deadline = Math.ceil(Date.now() / 1000) + deadlineFromNow
let estimate, method, args, value let estimate, method, args, value
if (independentField === INPUT) { if (independentField === INPUT) {
@ -645,15 +646,12 @@ export default function ExchangePage({ initialCurrency, sending = false, params
const [customSlippageError, setcustomSlippageError] = useState('') const [customSlippageError, setcustomSlippageError] = useState('')
const allBalances = useFetchAllBalances()
const toggleWalletModal = useWalletModalToggle() const toggleWalletModal = useWalletModalToggle()
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={() => {
@ -702,7 +700,6 @@ export default function ExchangePage({ initialCurrency, sending = false, params
</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 => {
@ -764,6 +761,8 @@ export default function ExchangePage({ initialCurrency, sending = false, params
rawSlippage={rawSlippage} rawSlippage={rawSlippage}
slippageWarning={slippageWarning} slippageWarning={slippageWarning}
highSlippageWarning={highSlippageWarning} highSlippageWarning={highSlippageWarning}
setDeadline={setDeadlineFromNow}
deadline={deadlineFromNow}
inputError={inputError} inputError={inputError}
independentError={independentError} independentError={independentError}
inputCurrency={inputCurrency} inputCurrency={inputCurrency}

@ -260,7 +260,7 @@ const LastSummaryText = styled.div`
const SlippageSelector = styled.div` const SlippageSelector = styled.div`
background-color: ${({ theme }) => darken(0.04, theme.concreteGray)}; background-color: ${({ theme }) => darken(0.04, theme.concreteGray)};
padding: 1rem 1.25rem 1rem 1.25rem; padding: 1rem 1.25rem 1rem 1.25rem;
border-radius: 12px; border-radius: 12px 12px 0 0;
` `
const Percent = styled.div` const Percent = styled.div`
@ -294,6 +294,14 @@ const ValueWrapper = styled.span`
font-variant: tabular-nums; font-variant: tabular-nums;
` `
const DeadlineSelector = styled.div`
background-color: ${({ theme }) => darken(0.04, theme.concreteGray)};
padding: 1rem 1.25rem 1rem 1.25rem;
border-radius: 0 0 12px 12px;
`
const DeadlineRow = SlippageRow
const DeadlineInput = OptionCustom
export default function TransactionDetails(props) { export default function TransactionDetails(props) {
const { t } = useTranslation() const { t } = useTranslation()
@ -314,6 +322,8 @@ export default function TransactionDetails(props) {
} }
}) })
const [deadlineInput, setDeadlineInput] = useState('')
function renderSummary() { function renderSummary() {
let contextualInfo = '' let contextualInfo = ''
let isError = false let isError = false
@ -492,6 +502,14 @@ export default function TransactionDetails(props) {
</BottomError> </BottomError>
</SlippageRow> </SlippageRow>
</SlippageSelector> </SlippageSelector>
<DeadlineSelector>
Set swap deadline (minutes from now)
<DeadlineRow wrap>
<DeadlineInput>
<Input placeholder={'Deadline'} value={deadlineInput} onChange={parseDeadlineInput} />
</DeadlineInput>
</DeadlineRow>
</DeadlineSelector>
</> </>
) )
} }
@ -507,6 +525,7 @@ export default function TransactionDetails(props) {
const setRawSlippage = props.setRawSlippage const setRawSlippage = props.setRawSlippage
const setRawTokenSlippage = props.setRawTokenSlippage const setRawTokenSlippage = props.setRawTokenSlippage
const setcustomSlippageError = props.setcustomSlippageError const setcustomSlippageError = props.setcustomSlippageError
const setDeadline = props.setDeadline
const updateSlippage = useCallback( const updateSlippage = useCallback(
newSlippage => { newSlippage => {
@ -604,6 +623,22 @@ export default function TransactionDetails(props) {
} }
} }
const [initialDeadline] = useState(props.deadline)
useEffect(() => {
setDeadlineInput(initialDeadline / 60)
}, [initialDeadline])
const parseDeadlineInput = e => {
const input = e.target.value
const acceptableValues = [/^$/, /^\d+$/]
if (acceptableValues.some(re => re.test(input))) {
setDeadlineInput(input)
setDeadline(parseInt(input) * 60)
}
}
const b = text => <Bold>{text}</Bold> const b = text => <Bold>{text}</Bold>
const renderTransactionDetails = () => { const renderTransactionDetails = () => {

@ -1,100 +0,0 @@
import React, { createContext, useContext, useReducer, useMemo, useCallback } from 'react'
import { ethers } from 'ethers'
import { getTokenReserves, getMarketDetails, BigNumber } from '@uniswap/sdk'
import { useWeb3React } from '../hooks'
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 { library, chainId, account } = useWeb3React()
const allTokens = useAllTokenDetails()
const [state, { update }] = useAllBalancesContext()
const { allBalanceData } = safeAccess(state, [chainId, 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, chainId, account)
}
}
useMemo(getData, [account])
return allBalanceData
}

@ -2,14 +2,12 @@ import React, { createContext, useContext, useReducer, useMemo, useCallback, use
import { useWeb3React } from '../hooks' import { useWeb3React } from '../hooks'
import { safeAccess } from '../utils' import { safeAccess } from '../utils'
import { getUSDPrice } from '../utils/price'
const BLOCK_NUMBER = 'BLOCK_NUMBER' const BLOCK_NUMBER = 'BLOCK_NUMBER'
const USD_PRICE = 'USD_PRICE' const USD_PRICE = 'USD_PRICE'
const WALLET_MODAL_OPEN = 'WALLET_MODAL_OPEN' const WALLET_MODAL_OPEN = 'WALLET_MODAL_OPEN'
const UPDATE_BLOCK_NUMBER = 'UPDATE_BLOCK_NUMBER' const UPDATE_BLOCK_NUMBER = 'UPDATE_BLOCK_NUMBER'
const UPDATE_USD_PRICE = 'UPDATE_USD_PRICE'
const TOGGLE_WALLET_MODAL = 'TOGGLE_WALLET_MODAL' const TOGGLE_WALLET_MODAL = 'TOGGLE_WALLET_MODAL'
const ApplicationContext = createContext() const ApplicationContext = createContext()
@ -30,16 +28,7 @@ function reducer(state, { type, payload }) {
} }
} }
} }
case UPDATE_USD_PRICE: {
const { networkId, USDPrice } = payload
return {
...state,
[USD_PRICE]: {
...(safeAccess(state, [USD_PRICE]) || {}),
[networkId]: USDPrice
}
}
}
case TOGGLE_WALLET_MODAL: { case TOGGLE_WALLET_MODAL: {
return { ...state, [WALLET_MODAL_OPEN]: !state[WALLET_MODAL_OPEN] } return { ...state, [WALLET_MODAL_OPEN]: !state[WALLET_MODAL_OPEN] }
} }
@ -60,20 +49,15 @@ export default function Provider({ children }) {
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 } })
}, [])
const toggleWalletModal = useCallback(() => { const toggleWalletModal = useCallback(() => {
dispatch({ type: TOGGLE_WALLET_MODAL }) dispatch({ type: TOGGLE_WALLET_MODAL })
}, []) }, [])
return ( return (
<ApplicationContext.Provider <ApplicationContext.Provider
value={useMemo(() => [state, { updateBlockNumber, updateUSDPrice, toggleWalletModal }], [ value={useMemo(() => [state, { updateBlockNumber, toggleWalletModal }], [
state, state,
updateBlockNumber, updateBlockNumber,
updateUSDPrice,
toggleWalletModal toggleWalletModal
])} ])}
> >
@ -85,27 +69,7 @@ export default function Provider({ children }) {
export function Updater() { export function Updater() {
const { library, chainId } = useWeb3React() const { library, chainId } = useWeb3React()
const globalBlockNumber = useBlockNumber() const [, { updateBlockNumber }] = useApplicationContext()
const [, { updateBlockNumber, updateUSDPrice }] = useApplicationContext()
// update usd price
useEffect(() => {
if (library && chainId === 1) {
let stale = false
getUSDPrice(library)
.then(([price]) => {
if (!stale) {
updateUSDPrice(chainId, price)
}
})
.catch(() => {
if (!stale) {
updateUSDPrice(chainId, null)
}
})
}
}, [globalBlockNumber, library, chainId, updateUSDPrice])
// update block number // update block number
useEffect(() => { useEffect(() => {
@ -148,14 +112,6 @@ export function useBlockNumber() {
return safeAccess(state, [BLOCK_NUMBER, chainId]) return safeAccess(state, [BLOCK_NUMBER, chainId])
} }
export function useUSDPrice() {
const { chainId } = useWeb3React()
const [state] = useApplicationContext()
return safeAccess(state, [USD_PRICE, chainId])
}
export function useWalletModalOpen() { export function useWalletModalOpen() {
const [state] = useApplicationContext() const [state] = useApplicationContext()

@ -1,11 +1,15 @@
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react' import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect, useRef, useState } from 'react'
import { BigNumber } from '@uniswap/sdk'
import { useWeb3React } from '../hooks' import { useWeb3React } from '../hooks'
import { safeAccess, isAddress, getEtherBalance, getTokenBalance } from '../utils' import { safeAccess, isAddress, getEtherBalance, getTokenBalance } from '../utils'
import { useBlockNumber } from './Application' import { useBlockNumber } from './Application'
import { useTokenDetails } from './Tokens' import { useTokenDetails, useAllTokenDetails } from './Tokens'
import { getUSDPrice } from '../utils/price'
const UPDATE = 'UPDATE' const UPDATE = 'UPDATE'
const UPDATE_ALL_FOR_ACCOUNT = 'UPDATE_ALL_FOR_ACCOUNT'
const UPDATE_ALL_FOR_EXCHANGES = 'UPDATE_ALL_FOR_EXCHANGES'
const BalancesContext = createContext() const BalancesContext = createContext()
@ -31,6 +35,40 @@ function reducer(state, { type, payload }) {
} }
} }
} }
case UPDATE_ALL_FOR_ACCOUNT: {
const { networkId, address, tokenAddresses, values } = payload
return {
...state,
[networkId]: {
...(safeAccess(state, [networkId]) || {}),
[address]: {
...tokenAddresses.reduce((accumulator, currentValue, i) => {
accumulator[currentValue] = { value: values[i] }
return accumulator
}, {}),
...(safeAccess(state, [networkId, address]) || {})
}
}
}
}
case UPDATE_ALL_FOR_EXCHANGES: {
const { networkId, exchangeAddresses, tokenAddresses, values } = payload
return {
...state,
[networkId]: {
...(safeAccess(state, [networkId]) || {}),
...exchangeAddresses.reduce((accumulator, currentValue, i) => {
accumulator[currentValue] = {
...safeAccess(state, [networkId, currentValue]),
[tokenAddresses[i]]: {
value: values[i]
}
}
return accumulator
}, {})
}
}
}
default: { default: {
throw Error(`Unexpected action type in BalancesContext reducer: '${type}'.`) throw Error(`Unexpected action type in BalancesContext reducer: '${type}'.`)
} }
@ -44,13 +82,111 @@ export default function Provider({ children }) {
dispatch({ type: UPDATE, payload: { networkId, address, tokenAddress, value, blockNumber } }) dispatch({ type: UPDATE, payload: { networkId, address, tokenAddress, value, blockNumber } })
}, []) }, [])
const updateAllForAccount = useCallback((networkId, address, tokenAddresses, values) => {
dispatch({ type: UPDATE_ALL_FOR_ACCOUNT, payload: { networkId, address, tokenAddresses, values } })
}, [])
const updateAllForExchanges = useCallback((networkId, exchangeAddresses, tokenAddresses, values) => {
dispatch({ type: UPDATE_ALL_FOR_EXCHANGES, payload: { networkId, exchangeAddresses, tokenAddresses, values } })
}, [])
return ( return (
<BalancesContext.Provider value={useMemo(() => [state, { update }], [state, update])}> <BalancesContext.Provider
value={useMemo(() => [state, { update, updateAllForAccount, updateAllForExchanges }], [
state,
update,
updateAllForAccount,
updateAllForExchanges
])}
>
{children} {children}
</BalancesContext.Provider> </BalancesContext.Provider>
) )
} }
const STAGGER_TIME = 2500
export function Updater() {
const { library, chainId, account } = useWeb3React()
const allTokens = useAllTokenDetails()
const [state, { updateAllForAccount, updateAllForExchanges }] = useBalancesContext()
const stateRef = useRef(state)
stateRef.current = state
useEffect(() => {
const getData = async () => {
if (chainId && library && account) {
// get 1 eth + all token balances for the account
Promise.all(
Object.keys(allTokens).map(async tokenAddress => {
await new Promise(resolve => setTimeout(resolve, STAGGER_TIME * Math.random()))
const { value: existingValue } = safeAccess(stateRef.current, [chainId, account, tokenAddress]) || {}
return (
existingValue ||
(await (tokenAddress === 'ETH'
? getEtherBalance(account, library).catch(() => null)
: getTokenBalance(tokenAddress, account, library).catch(() => null)))
)
})
).then(balances => {
updateAllForAccount(chainId, account, Object.keys(allTokens), balances)
})
const allTokensWithAnExchange = Object.keys(allTokens).filter(tokenAddress => tokenAddress !== 'ETH')
// get all eth balances for all exchanges
Promise.all(
allTokensWithAnExchange.map(async tokenAddress => {
await new Promise(resolve => setTimeout(resolve, STAGGER_TIME * Math.random()))
const exchangeAddress = allTokens[tokenAddress].exchangeAddress
const { value: existingValue } = safeAccess(stateRef.current, [chainId, exchangeAddress, 'ETH']) || {}
return existingValue || (await getEtherBalance(exchangeAddress, library).catch(() => null))
})
).then(ethBalances => {
updateAllForExchanges(
chainId,
allTokensWithAnExchange.map(tokenAddress => allTokens[tokenAddress].exchangeAddress),
Array(allTokensWithAnExchange.length).fill('ETH'),
ethBalances
)
})
// get all token balances for all exchanges
Promise.all(
allTokensWithAnExchange.map(async tokenAddress => {
await new Promise(resolve => setTimeout(resolve, STAGGER_TIME * Math.random()))
const exchangeAddress = allTokens[tokenAddress].exchangeAddress
const { value: existingValue } =
safeAccess(stateRef.current, [chainId, exchangeAddress, tokenAddress]) || {}
return existingValue || (await getTokenBalance(tokenAddress, exchangeAddress, library).catch(() => null))
})
).then(tokenBalances => {
updateAllForExchanges(
chainId,
allTokensWithAnExchange.map(tokenAddress => allTokens[tokenAddress].exchangeAddress),
allTokensWithAnExchange.map(tokenAddress => tokenAddress),
tokenBalances
)
})
}
}
getData()
}, [chainId, library, account, allTokens, updateAllForAccount, updateAllForExchanges])
return null
}
export function useAllBalances() {
const { chainId } = useWeb3React()
const [state] = useBalancesContext()
const balances = safeAccess(state, [chainId]) || {}
return balances
}
export function useAddressBalance(address, tokenAddress) { export function useAddressBalance(address, tokenAddress) {
const { library, chainId } = useWeb3React() const { library, chainId } = useWeb3React()
@ -96,3 +232,79 @@ export function useExchangeReserves(tokenAddress) {
return { reserveETH, reserveToken } return { reserveETH, reserveToken }
} }
const buildReserveObject = (chainId, tokenAddress, ethReserveAmount, tokenReserveAmount, decimals) => ({
token: {
chainId,
address: tokenAddress,
decimals
},
ethReserve: {
token: {
chainId,
decimals: 18
},
amount: ethReserveAmount
},
tokenReserve: {
token: {
chainId,
address: tokenAddress,
decimals
},
amount: tokenReserveAmount
}
})
const daiTokenAddress = '0x6B175474E89094C44Da98b954EedeAC495271d0F'
const daiExchangeAddress = '0x2a1530C4C41db0B0b2bB646CB5Eb1A67b7158667'
const usdcTokenAddress = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
const usdcExchangeAddress = '0x97deC872013f6B5fB443861090ad931542878126'
const tusdTokenAddress = '0x0000000000085d4780B73119b644AE5ecd22b376'
const tusdExchangeAddress = '0x5048b9d01097498Fd72F3F14bC9Bc74A5aAc8fA7'
export function useETHPriceInUSD() {
const { chainId } = useWeb3React()
let daiReserveETH = useAddressBalance(daiExchangeAddress, 'ETH')
let daiReserveToken = useAddressBalance(daiExchangeAddress, daiTokenAddress)
let usdcReserveETH = useAddressBalance(usdcExchangeAddress, 'ETH')
let usdcReserveToken = useAddressBalance(usdcExchangeAddress, usdcTokenAddress)
let tusdReserveETH = useAddressBalance(tusdExchangeAddress, 'ETH')
let tusdReserveToken = useAddressBalance(tusdExchangeAddress, tusdTokenAddress)
const [price, setPrice] = useState()
useEffect(() => {
if (daiReserveETH && daiReserveToken && usdcReserveETH && usdcReserveToken && tusdReserveETH && tusdReserveToken) {
const daiReservesObject = buildReserveObject(
chainId,
daiTokenAddress,
new BigNumber(daiReserveETH.toString()),
new BigNumber(daiReserveToken.toString()),
18
)
const tusdReservesObject = buildReserveObject(
chainId,
tusdTokenAddress,
new BigNumber(tusdReserveETH.toString()),
new BigNumber(tusdReserveToken.toString()),
18
)
const usdcReservesObject = buildReserveObject(
chainId,
usdcTokenAddress,
new BigNumber(usdcReserveETH.toString()),
new BigNumber(usdcReserveToken.toString()),
6
)
const stablecoinReserves = [daiReservesObject, usdcReservesObject, tusdReservesObject]
try {
setPrice(getUSDPrice(stablecoinReserves))
} catch {
setPrice(null)
}
}
}, [daiReserveETH, daiReserveToken, usdcReserveETH, usdcReserveToken, tusdReserveETH, tusdReserveToken, chainId])
return price
}

@ -1,5 +1,4 @@
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react' import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
import { ethers } from 'ethers'
import { useWeb3React } from '../hooks' import { useWeb3React } from '../hooks'
import { import {
@ -615,23 +614,10 @@ export function useTokenDetails(tokenAddress) {
return { name, symbol, decimals, exchangeAddress } return { name, symbol, decimals, exchangeAddress }
} }
export function useAllTokenDetails(requireExchange = true) { export function useAllTokenDetails() {
const { chainId } = useWeb3React() const { chainId } = useWeb3React()
const [state] = useTokensContext() const [state] = useTokensContext()
const tokenDetails = { ...ETH, ...(safeAccess(state, [chainId]) || {}) }
return requireExchange return useMemo(() => ({ ...ETH, ...(safeAccess(state, [chainId]) || {}) }), [state, chainId])
? Object.keys(tokenDetails)
.filter(
tokenAddress =>
tokenAddress === 'ETH' ||
(safeAccess(tokenDetails, [tokenAddress, EXCHANGE_ADDRESS]) &&
safeAccess(tokenDetails, [tokenAddress, EXCHANGE_ADDRESS]) !== ethers.constants.AddressZero)
)
.reduce((accumulator, tokenAddress) => {
accumulator[tokenAddress] = tokenDetails[tokenAddress]
return accumulator
}, {})
: tokenDetails
} }

@ -8,10 +8,9 @@ import { NetworkContextName } from './constants'
import LocalStorageContextProvider, { Updater as LocalStorageContextUpdater } from './contexts/LocalStorage' import LocalStorageContextProvider, { Updater as LocalStorageContextUpdater } from './contexts/LocalStorage'
import ApplicationContextProvider, { Updater as ApplicationContextUpdater } from './contexts/Application' import ApplicationContextProvider, { Updater as ApplicationContextUpdater } from './contexts/Application'
import TransactionContextProvider, { Updater as TransactionContextUpdater } from './contexts/Transactions' import TransactionContextProvider, { Updater as TransactionContextUpdater } from './contexts/Transactions'
import BalancesContextProvider, { Updater as BalancesContextUpdater } from './contexts/Balances'
import TokensContextProvider from './contexts/Tokens' import TokensContextProvider from './contexts/Tokens'
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 ThemeProvider, { GlobalStyle } from './theme' import ThemeProvider, { GlobalStyle } from './theme'
import './i18n' import './i18n'
@ -38,9 +37,7 @@ function ContextProviders({ children }) {
<TransactionContextProvider> <TransactionContextProvider>
<TokensContextProvider> <TokensContextProvider>
<BalancesContextProvider> <BalancesContextProvider>
<AllBalancesContextProvider> <AllowancesContextProvider>{children}</AllowancesContextProvider>
<AllowancesContextProvider>{children}</AllowancesContextProvider>
</AllBalancesContextProvider>
</BalancesContextProvider> </BalancesContextProvider>
</TokensContextProvider> </TokensContextProvider>
</TransactionContextProvider> </TransactionContextProvider>
@ -55,6 +52,7 @@ function Updaters() {
<LocalStorageContextUpdater /> <LocalStorageContextUpdater />
<ApplicationContextUpdater /> <ApplicationContextUpdater />
<TransactionContextUpdater /> <TransactionContextUpdater />
<BalancesContextUpdater />
</> </>
) )
} }

@ -15,7 +15,6 @@ import { brokenTokens } from '../../constants'
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'
@ -567,8 +566,6 @@ export default function AddLiquidity({ params }) {
const isActive = active && account const isActive = active && account
const isValid = (inputError === null || outputError === null) && !showUnlock && !brokenTokenWarning const isValid = (inputError === null || outputError === null) && !showUnlock && !brokenTokenWarning
const allBalances = useFetchAllBalances()
return ( return (
<> <>
{isNewExchange ? ( {isNewExchange ? (
@ -585,7 +582,6 @@ export default function AddLiquidity({ params }) {
<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 } })
@ -613,7 +609,6 @@ export default function AddLiquidity({ params }) {
</OversizedPanel> </OversizedPanel>
<CurrencyInputPanel <CurrencyInputPanel
title={t('deposit')} title={t('deposit')}
allBalances={allBalances}
description={isNewExchange ? '' : outputValue ? `(${t('estimated')})` : ''} description={isNewExchange ? '' : outputValue ? `(${t('estimated')})` : ''}
extraText={ extraText={
outputBalance && decimals && formatBalance(amountFormatter(outputBalance, decimals, Math.min(decimals, 4))) outputBalance && decimals && formatBalance(amountFormatter(outputBalance, decimals, Math.min(decimals, 4)))

@ -14,7 +14,6 @@ import ArrowDown from '../../assets/svg/SVGArrowDown'
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
@ -334,13 +333,10 @@ export default function RemoveLiquidity({ params }) {
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) {
@ -363,7 +359,6 @@ export default function RemoveLiquidity({ params }) {
</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={() =>

@ -1,4 +1,4 @@
import { getTokenReserves, getMarketDetails } from '@uniswap/sdk' import { getMarketDetails } from '@uniswap/sdk'
import { getMedian, getMean } from './math' import { getMedian, getMean } from './math'
const DAI = 'DAI' const DAI = 'DAI'
@ -7,37 +7,29 @@ const TUSD = 'TUSD'
const USD_STABLECOINS = [DAI, USDC, TUSD] const USD_STABLECOINS = [DAI, USDC, TUSD]
const USD_STABLECOIN_ADDRESSES = [
'0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359',
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
'0x8dd5fbCe2F6a956C3022bA3663759011Dd51e73E'
]
function forEachStablecoin(runner) { function forEachStablecoin(runner) {
return USD_STABLECOINS.map((stablecoin, index) => runner(index, stablecoin)) return USD_STABLECOINS.map((stablecoin, index) => runner(index, stablecoin))
} }
export async function getUSDPrice(library) { export function getUSDPrice(reserves) {
return Promise.all(forEachStablecoin(i => getTokenReserves(USD_STABLECOIN_ADDRESSES[i], library))).then(reserves => { const marketDetails = forEachStablecoin(i => getMarketDetails(reserves[i], undefined))
const ethReserves = forEachStablecoin(i => reserves[i].ethReserve.amount) const ethPrices = forEachStablecoin(i => marketDetails[i].marketRate.rateInverted)
const marketDetails = forEachStablecoin(i => getMarketDetails(reserves[i], undefined))
const ethPrices = forEachStablecoin(i => marketDetails[i].marketRate.rateInverted) const [median] = getMedian(ethPrices)
const [mean] = getMean(ethPrices)
const [weightedMean] = getMean(
ethPrices,
forEachStablecoin(i => reserves[i].ethReserve.amount)
)
const [median, medianWeights] = getMedian(ethPrices) // const _stablecoinWeights = [
const [mean, meanWeights] = getMean(ethPrices) // getMean([medianWeights[0], meanWeights[0], weightedMeanWeights[0]])[0],
const [weightedMean, weightedMeanWeights] = getMean(ethPrices, ethReserves) // 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 }), {})
const ethPrice = getMean([median, mean, weightedMean])[0] return 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]
})
} }