Merge branch 'beta' into production
This commit is contained in:
commit
31499ee2b1
12
README.md
12
README.md
@ -11,10 +11,18 @@ This an an open source interface for Uniswap - a protocol for decentralized exch
|
||||
- Twitter: [@UniswapExchange](https://twitter.com/UniswapExchange)
|
||||
- Reddit: [/r/Uniswap](https://www.reddit.com/r/UniSwap/)
|
||||
- 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)
|
||||
|
||||
## 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).
|
||||
|
||||
## To Start Development
|
||||
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
|
||||
|
||||
|
@ -24,7 +24,7 @@ import { ReactComponent as Close } from '../../assets/images/x.svg'
|
||||
import { transparentize } from 'polished'
|
||||
import { Spinner } from '../../theme'
|
||||
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)
|
||||
|
||||
@ -439,7 +439,7 @@ export default function CurrencyInputPanel({
|
||||
)
|
||||
}
|
||||
|
||||
function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect, allBalances }) {
|
||||
function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect }) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
@ -450,19 +450,24 @@ function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect, allBalances })
|
||||
const { account } = useWeb3React()
|
||||
|
||||
// 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 => {
|
||||
if (
|
||||
ethPrice &&
|
||||
allBalances &&
|
||||
allBalances[k] &&
|
||||
allBalances[k].ethRate &&
|
||||
!allBalances[k].ethRate.isNaN() &&
|
||||
allBalances[k].balance
|
||||
) {
|
||||
const USDRate = ethPrice.times(allBalances[k].ethRate)
|
||||
const balanceBigNumber = new BigNumber(allBalances[k].balance.toString())
|
||||
if (ethPrice && allBalances[account] && allBalances[account][k]) {
|
||||
let ethRate = 1 // default for ETH
|
||||
let exchangeDetails = allBalances[allTokens[k].exchangeAddress]
|
||||
if (exchangeDetails && exchangeDetails[k] && exchangeDetails['ETH']) {
|
||||
const tokenBalance = new BigNumber(exchangeDetails[k].value.toString())
|
||||
const ethBalance = new BigNumber(exchangeDetails['ETH'].value.toString())
|
||||
ethRate = ethBalance.div(tokenBalance)
|
||||
}
|
||||
const USDRate = ethPrice
|
||||
.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))
|
||||
return usdBalance
|
||||
} else {
|
||||
@ -506,11 +511,11 @@ function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect, allBalances })
|
||||
let balance
|
||||
let usdBalance
|
||||
// only update if we have data
|
||||
if (k === 'ETH' && allBalances && allBalances[k]) {
|
||||
balance = formatEthBalance(allBalances[k].balance)
|
||||
if (k === 'ETH' && allBalances[account] && allBalances[account][k]) {
|
||||
balance = formatEthBalance(allBalances[account][k].value)
|
||||
usdBalance = usdAmounts[k]
|
||||
} else if (allBalances && allBalances[k]) {
|
||||
balance = formatTokenBalance(allBalances[k].balance, allTokens[k].decimals)
|
||||
} else if (allBalances[account] && allBalances[account][k]) {
|
||||
balance = formatTokenBalance(allBalances[account][k].value, allTokens[k].decimals)
|
||||
usdBalance = usdAmounts[k]
|
||||
}
|
||||
return {
|
||||
@ -521,7 +526,7 @@ function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect, allBalances })
|
||||
usdBalance: usdBalance
|
||||
}
|
||||
})
|
||||
}, [allBalances, allTokens, usdAmounts])
|
||||
}, [allBalances, allTokens, usdAmounts, account])
|
||||
|
||||
const filteredTokenList = useMemo(() => {
|
||||
return tokenList.filter(tokenEntry => {
|
||||
@ -580,7 +585,13 @@ function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect, allBalances })
|
||||
'-'
|
||||
)}
|
||||
<TokenRowUsd>
|
||||
{usdBalance ? (usdBalance.lt(0.01) ? '<$0.01' : '$' + formatToUsd(usdBalance)) : ''}
|
||||
{usdBalance
|
||||
? usdBalance.isZero()
|
||||
? ''
|
||||
: usdBalance.lt(0.01)
|
||||
? '<$0.01'
|
||||
: '$' + formatToUsd(usdBalance)
|
||||
: ''}
|
||||
</TokenRowUsd>
|
||||
</TokenRowRight>
|
||||
</TokenModalRow>
|
||||
|
@ -17,7 +17,6 @@ import { useExchangeContract } from '../../hooks'
|
||||
import { useTokenDetails } from '../../contexts/Tokens'
|
||||
import { useTransactionAdder } from '../../contexts/Transactions'
|
||||
import { useAddressBalance, useExchangeReserves } from '../../contexts/Balances'
|
||||
import { useFetchAllBalances } from '../../contexts/AllBalances'
|
||||
import { useAddressAllowance } from '../../contexts/Allowances'
|
||||
import { useWalletModalToggle } from '../../contexts/Application'
|
||||
|
||||
@ -33,7 +32,7 @@ const ALLOWED_SLIPPAGE_DEFAULT = 100
|
||||
const TOKEN_ALLOWED_SLIPPAGE_DEFAULT = 100
|
||||
|
||||
// 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
|
||||
const GAS_MARGIN = ethers.utils.bigNumberify(1000)
|
||||
@ -267,6 +266,8 @@ export default function ExchangePage({ initialCurrency, sending = false, params
|
||||
return ''
|
||||
}
|
||||
|
||||
const [deadlineFromNow, setDeadlineFromNow] = useState(DEFAULT_DEADLINE_FROM_NOW)
|
||||
|
||||
const [rawSlippage, setRawSlippage] = useState(() => initialSlippage())
|
||||
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 isValid = sending
|
||||
? exchangeRate && inputError === null && independentError === null && recipientError === null
|
||||
: exchangeRate && inputError === null && independentError === null
|
||||
? exchangeRate && inputError === null && independentError === null && recipientError === null && deadlineFromNow
|
||||
: exchangeRate && inputError === null && independentError === null && deadlineFromNow
|
||||
|
||||
const estimatedText = `(${t('estimated')})`
|
||||
function formatBalance(value) {
|
||||
@ -563,7 +564,7 @@ export default function ExchangePage({ initialCurrency, sending = false, params
|
||||
}
|
||||
|
||||
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
|
||||
if (independentField === INPUT) {
|
||||
@ -645,15 +646,12 @@ export default function ExchangePage({ initialCurrency, sending = false, params
|
||||
|
||||
const [customSlippageError, setcustomSlippageError] = useState('')
|
||||
|
||||
const allBalances = useFetchAllBalances()
|
||||
|
||||
const toggleWalletModal = useWalletModalToggle()
|
||||
|
||||
return (
|
||||
<>
|
||||
<CurrencyInputPanel
|
||||
title={t('input')}
|
||||
allBalances={allBalances}
|
||||
description={inputValueFormatted && independentField === OUTPUT ? estimatedText : ''}
|
||||
extraText={inputBalanceFormatted && formatBalance(inputBalanceFormatted)}
|
||||
extraTextClickHander={() => {
|
||||
@ -702,7 +700,6 @@ export default function ExchangePage({ initialCurrency, sending = false, params
|
||||
</OversizedPanel>
|
||||
<CurrencyInputPanel
|
||||
title={t('output')}
|
||||
allBalances={allBalances}
|
||||
description={outputValueFormatted && independentField === INPUT ? estimatedText : ''}
|
||||
extraText={outputBalanceFormatted && formatBalance(outputBalanceFormatted)}
|
||||
onCurrencySelected={outputCurrency => {
|
||||
@ -764,6 +761,8 @@ export default function ExchangePage({ initialCurrency, sending = false, params
|
||||
rawSlippage={rawSlippage}
|
||||
slippageWarning={slippageWarning}
|
||||
highSlippageWarning={highSlippageWarning}
|
||||
setDeadline={setDeadlineFromNow}
|
||||
deadline={deadlineFromNow}
|
||||
inputError={inputError}
|
||||
independentError={independentError}
|
||||
inputCurrency={inputCurrency}
|
||||
|
@ -260,7 +260,7 @@ const LastSummaryText = styled.div`
|
||||
const SlippageSelector = styled.div`
|
||||
background-color: ${({ theme }) => darken(0.04, theme.concreteGray)};
|
||||
padding: 1rem 1.25rem 1rem 1.25rem;
|
||||
border-radius: 12px;
|
||||
border-radius: 12px 12px 0 0;
|
||||
`
|
||||
|
||||
const Percent = styled.div`
|
||||
@ -294,6 +294,14 @@ const ValueWrapper = styled.span`
|
||||
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) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@ -314,6 +322,8 @@ export default function TransactionDetails(props) {
|
||||
}
|
||||
})
|
||||
|
||||
const [deadlineInput, setDeadlineInput] = useState('')
|
||||
|
||||
function renderSummary() {
|
||||
let contextualInfo = ''
|
||||
let isError = false
|
||||
@ -492,6 +502,14 @@ export default function TransactionDetails(props) {
|
||||
</BottomError>
|
||||
</SlippageRow>
|
||||
</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 setRawTokenSlippage = props.setRawTokenSlippage
|
||||
const setcustomSlippageError = props.setcustomSlippageError
|
||||
const setDeadline = props.setDeadline
|
||||
|
||||
const updateSlippage = useCallback(
|
||||
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 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 { safeAccess } from '../utils'
|
||||
import { getUSDPrice } from '../utils/price'
|
||||
|
||||
const BLOCK_NUMBER = 'BLOCK_NUMBER'
|
||||
const USD_PRICE = 'USD_PRICE'
|
||||
const WALLET_MODAL_OPEN = 'WALLET_MODAL_OPEN'
|
||||
|
||||
const UPDATE_BLOCK_NUMBER = 'UPDATE_BLOCK_NUMBER'
|
||||
const UPDATE_USD_PRICE = 'UPDATE_USD_PRICE'
|
||||
const TOGGLE_WALLET_MODAL = 'TOGGLE_WALLET_MODAL'
|
||||
|
||||
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: {
|
||||
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 } })
|
||||
}, [])
|
||||
|
||||
const updateUSDPrice = useCallback((networkId, USDPrice) => {
|
||||
dispatch({ type: UPDATE_USD_PRICE, payload: { networkId, USDPrice } })
|
||||
}, [])
|
||||
|
||||
const toggleWalletModal = useCallback(() => {
|
||||
dispatch({ type: TOGGLE_WALLET_MODAL })
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<ApplicationContext.Provider
|
||||
value={useMemo(() => [state, { updateBlockNumber, updateUSDPrice, toggleWalletModal }], [
|
||||
value={useMemo(() => [state, { updateBlockNumber, toggleWalletModal }], [
|
||||
state,
|
||||
updateBlockNumber,
|
||||
updateUSDPrice,
|
||||
toggleWalletModal
|
||||
])}
|
||||
>
|
||||
@ -85,27 +69,7 @@ export default function Provider({ children }) {
|
||||
export function Updater() {
|
||||
const { library, chainId } = useWeb3React()
|
||||
|
||||
const globalBlockNumber = useBlockNumber()
|
||||
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])
|
||||
const [, { updateBlockNumber }] = useApplicationContext()
|
||||
|
||||
// update block number
|
||||
useEffect(() => {
|
||||
@ -148,14 +112,6 @@ export function useBlockNumber() {
|
||||
return safeAccess(state, [BLOCK_NUMBER, chainId])
|
||||
}
|
||||
|
||||
export function useUSDPrice() {
|
||||
const { chainId } = useWeb3React()
|
||||
|
||||
const [state] = useApplicationContext()
|
||||
|
||||
return safeAccess(state, [USD_PRICE, chainId])
|
||||
}
|
||||
|
||||
export function useWalletModalOpen() {
|
||||
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 { safeAccess, isAddress, getEtherBalance, getTokenBalance } from '../utils'
|
||||
import { useBlockNumber } from './Application'
|
||||
import { useTokenDetails } from './Tokens'
|
||||
import { useTokenDetails, useAllTokenDetails } from './Tokens'
|
||||
import { getUSDPrice } from '../utils/price'
|
||||
|
||||
const UPDATE = 'UPDATE'
|
||||
const UPDATE_ALL_FOR_ACCOUNT = 'UPDATE_ALL_FOR_ACCOUNT'
|
||||
const UPDATE_ALL_FOR_EXCHANGES = 'UPDATE_ALL_FOR_EXCHANGES'
|
||||
|
||||
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: {
|
||||
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 } })
|
||||
}, [])
|
||||
|
||||
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 (
|
||||
<BalancesContext.Provider value={useMemo(() => [state, { update }], [state, update])}>
|
||||
<BalancesContext.Provider
|
||||
value={useMemo(() => [state, { update, updateAllForAccount, updateAllForExchanges }], [
|
||||
state,
|
||||
update,
|
||||
updateAllForAccount,
|
||||
updateAllForExchanges
|
||||
])}
|
||||
>
|
||||
{children}
|
||||
</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) {
|
||||
const { library, chainId } = useWeb3React()
|
||||
|
||||
@ -96,3 +232,79 @@ export function useExchangeReserves(tokenAddress) {
|
||||
|
||||
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 { ethers } from 'ethers'
|
||||
|
||||
import { useWeb3React } from '../hooks'
|
||||
import {
|
||||
@ -615,23 +614,10 @@ export function useTokenDetails(tokenAddress) {
|
||||
return { name, symbol, decimals, exchangeAddress }
|
||||
}
|
||||
|
||||
export function useAllTokenDetails(requireExchange = true) {
|
||||
export function useAllTokenDetails() {
|
||||
const { chainId } = useWeb3React()
|
||||
|
||||
const [state] = useTokensContext()
|
||||
const tokenDetails = { ...ETH, ...(safeAccess(state, [chainId]) || {}) }
|
||||
|
||||
return requireExchange
|
||||
? 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
|
||||
return useMemo(() => ({ ...ETH, ...(safeAccess(state, [chainId]) || {}) }), [state, chainId])
|
||||
}
|
||||
|
@ -8,10 +8,9 @@ import { NetworkContextName } from './constants'
|
||||
import LocalStorageContextProvider, { Updater as LocalStorageContextUpdater } from './contexts/LocalStorage'
|
||||
import ApplicationContextProvider, { Updater as ApplicationContextUpdater } from './contexts/Application'
|
||||
import TransactionContextProvider, { Updater as TransactionContextUpdater } from './contexts/Transactions'
|
||||
import BalancesContextProvider, { Updater as BalancesContextUpdater } from './contexts/Balances'
|
||||
import TokensContextProvider from './contexts/Tokens'
|
||||
import BalancesContextProvider from './contexts/Balances'
|
||||
import AllowancesContextProvider from './contexts/Allowances'
|
||||
import AllBalancesContextProvider from './contexts/AllBalances'
|
||||
import App from './pages/App'
|
||||
import ThemeProvider, { GlobalStyle } from './theme'
|
||||
import './i18n'
|
||||
@ -38,9 +37,7 @@ function ContextProviders({ children }) {
|
||||
<TransactionContextProvider>
|
||||
<TokensContextProvider>
|
||||
<BalancesContextProvider>
|
||||
<AllBalancesContextProvider>
|
||||
<AllowancesContextProvider>{children}</AllowancesContextProvider>
|
||||
</AllBalancesContextProvider>
|
||||
<AllowancesContextProvider>{children}</AllowancesContextProvider>
|
||||
</BalancesContextProvider>
|
||||
</TokensContextProvider>
|
||||
</TransactionContextProvider>
|
||||
@ -55,6 +52,7 @@ function Updaters() {
|
||||
<LocalStorageContextUpdater />
|
||||
<ApplicationContextUpdater />
|
||||
<TransactionContextUpdater />
|
||||
<BalancesContextUpdater />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ import { brokenTokens } from '../../constants'
|
||||
import { amountFormatter, calculateGasMargin } from '../../utils'
|
||||
import { useTransactionAdder } from '../../contexts/Transactions'
|
||||
import { useTokenDetails } from '../../contexts/Tokens'
|
||||
import { useFetchAllBalances } from '../../contexts/AllBalances'
|
||||
import { useAddressBalance, useExchangeReserves } from '../../contexts/Balances'
|
||||
import { useAddressAllowance } from '../../contexts/Allowances'
|
||||
|
||||
@ -567,8 +566,6 @@ export default function AddLiquidity({ params }) {
|
||||
const isActive = active && account
|
||||
const isValid = (inputError === null || outputError === null) && !showUnlock && !brokenTokenWarning
|
||||
|
||||
const allBalances = useFetchAllBalances()
|
||||
|
||||
return (
|
||||
<>
|
||||
{isNewExchange ? (
|
||||
@ -585,7 +582,6 @@ export default function AddLiquidity({ params }) {
|
||||
|
||||
<CurrencyInputPanel
|
||||
title={t('deposit')}
|
||||
allBalances={allBalances}
|
||||
extraText={inputBalance && formatBalance(amountFormatter(inputBalance, 18, 4))}
|
||||
onValueChange={inputValue => {
|
||||
dispatchAddLiquidityState({ type: 'UPDATE_VALUE', payload: { value: inputValue, field: INPUT } })
|
||||
@ -613,7 +609,6 @@ export default function AddLiquidity({ params }) {
|
||||
</OversizedPanel>
|
||||
<CurrencyInputPanel
|
||||
title={t('deposit')}
|
||||
allBalances={allBalances}
|
||||
description={isNewExchange ? '' : outputValue ? `(${t('estimated')})` : ''}
|
||||
extraText={
|
||||
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 { useTokenDetails } from '../../contexts/Tokens'
|
||||
import { useAddressBalance } from '../../contexts/Balances'
|
||||
import { useFetchAllBalances } from '../../contexts/AllBalances'
|
||||
import { calculateGasMargin, amountFormatter } from '../../utils'
|
||||
|
||||
// denominated in bips
|
||||
@ -334,13 +333,10 @@ export default function RemoveLiquidity({ params }) {
|
||||
|
||||
const marketRate = getMarketRate(exchangeETHBalance, exchangeTokenBalance, decimals)
|
||||
|
||||
const allBalances = useFetchAllBalances()
|
||||
|
||||
return (
|
||||
<>
|
||||
<CurrencyInputPanel
|
||||
title={t('poolTokens')}
|
||||
allBalances={allBalances}
|
||||
extraText={poolTokenBalance && formatBalance(amountFormatter(poolTokenBalance, 18, 4))}
|
||||
extraTextClickHander={() => {
|
||||
if (poolTokenBalance) {
|
||||
@ -363,7 +359,6 @@ export default function RemoveLiquidity({ params }) {
|
||||
</OversizedPanel>
|
||||
<CurrencyInputPanel
|
||||
title={t('output')}
|
||||
allBalances={allBalances}
|
||||
description={!!(ethWithdrawn && tokenWithdrawn) ? `(${t('estimated')})` : ''}
|
||||
key="remove-liquidity-input"
|
||||
renderInput={() =>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { getTokenReserves, getMarketDetails } from '@uniswap/sdk'
|
||||
import { getMarketDetails } from '@uniswap/sdk'
|
||||
import { getMedian, getMean } from './math'
|
||||
|
||||
const DAI = 'DAI'
|
||||
@ -7,37 +7,29 @@ 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))
|
||||
export function getUSDPrice(reserves) {
|
||||
const marketDetails = forEachStablecoin(i => getMarketDetails(reserves[i], undefined))
|
||||
const ethPrices = forEachStablecoin(i => marketDetails[i].marketRate.rateInverted)
|
||||
|
||||
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 [mean, meanWeights] = getMean(ethPrices)
|
||||
const [weightedMean, weightedMeanWeights] = getMean(ethPrices, ethReserves)
|
||||
// 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 }), {})
|
||||
|
||||
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]
|
||||
})
|
||||
return getMean([median, mean, weightedMean])[0]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user