Updates to Token Modal (#399)
This commit is contained in:
parent
be2012cdf5
commit
677537ca31
4
.gitignore
vendored
4
.gitignore
vendored
@ -24,4 +24,6 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
notes.txt
|
||||
.idea/
|
||||
.idea/
|
||||
|
||||
.vscode/
|
@ -7,9 +7,10 @@
|
||||
"dependencies": {
|
||||
"@reach/dialog": "^0.2.8",
|
||||
"@reach/tooltip": "^0.2.0",
|
||||
"@uniswap/sdk": "^1.0.0-beta.4",
|
||||
"copy-to-clipboard": "^3.2.0",
|
||||
"escape-string-regexp": "^2.0.0",
|
||||
"ethers": "^4.0.28",
|
||||
"ethers": "^4.0.33",
|
||||
"i18next": "^15.0.9",
|
||||
"i18next-browser-languagedetector": "^3.0.1",
|
||||
"i18next-xhr-backend": "^2.0.1",
|
||||
|
@ -18,7 +18,8 @@
|
||||
"unlock": "Unlock",
|
||||
"pending": "Pending",
|
||||
"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",
|
||||
"exchangeRate": "Exchange Rate",
|
||||
"unknownError": "Oops! An unknown error occurred. Please refresh the page, or visit from another browser or device.",
|
||||
@ -36,6 +37,7 @@
|
||||
"youWillReceive": "You will receive at least",
|
||||
"youAreBuying": "You are buying",
|
||||
"itWillCost": "It will cost at most",
|
||||
"forAtMost": "for at most",
|
||||
"insufficientBalance": "Insufficient Balance",
|
||||
"inputNotValid": "Not a valid input value",
|
||||
"differentToken": "Must be different token.",
|
||||
|
3
src/assets/images/circle-grey.svg
Normal file
3
src/assets/images/circle-grey.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<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>
|
After Width: | Height: | Size: 321 B |
1
src/assets/images/x.svg
Normal file
1
src/assets/images/x.svg
Normal file
@ -0,0 +1 @@
|
||||
<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>
|
After Width: | Height: | Size: 299 B |
@ -2,6 +2,7 @@ import React, { useState, useRef, useMemo } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ethers } from 'ethers'
|
||||
import { BigNumber } from '@uniswap/sdk'
|
||||
import styled from 'styled-components'
|
||||
import escapeStringRegex from 'escape-string-regexp'
|
||||
import { darken } from 'polished'
|
||||
@ -11,14 +12,18 @@ import { isMobile } from 'react-device-detect'
|
||||
|
||||
import { BorderlessInput } from '../../theme'
|
||||
import { useTokenContract } from '../../hooks'
|
||||
import { isAddress, calculateGasMargin } from '../../utils'
|
||||
import { isAddress, calculateGasMargin, formatToUsd, formatTokenBalance, formatEthBalance } from '../../utils'
|
||||
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
|
||||
import Modal from '../Modal'
|
||||
import TokenLogo from '../TokenLogo'
|
||||
import SearchIcon from '../../assets/images/magnifying-glass.svg'
|
||||
import { useTransactionAdder, usePendingApproval } from '../../contexts/Transactions'
|
||||
import { useTokenDetails, useAllTokenDetails } from '../../contexts/Tokens'
|
||||
import 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'
|
||||
|
||||
const GAS_MARGIN = ethers.utils.bigNumberify(1000)
|
||||
|
||||
@ -35,7 +40,6 @@ const SubCurrencySelect = styled.button`
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
`
|
||||
|
||||
const InputRow = styled.div`
|
||||
@ -52,8 +56,11 @@ const Input = styled(BorderlessInput)`
|
||||
`
|
||||
|
||||
const StyledBorderlessInput = styled(BorderlessInput)`
|
||||
min-height: 1.75rem;
|
||||
min-height: 2.5rem;
|
||||
flex-shrink: 0;
|
||||
text-align: left;
|
||||
padding-left: 1.6rem;
|
||||
background-color: ${({ theme }) => theme.concreteGray};
|
||||
`
|
||||
|
||||
const CurrencySelect = styled.button`
|
||||
@ -152,10 +159,27 @@ const TokenModal = styled.div`
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const ModalHeader = styled.div`
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 2rem;
|
||||
height: 60px;
|
||||
`
|
||||
|
||||
const CloseIcon = styled.div`
|
||||
position: absolute;
|
||||
right: 1.4rem;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
|
||||
const SearchContainer = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.mercuryGray};
|
||||
padding: 0.5rem 2rem;
|
||||
background-color: ${({ theme }) => theme.concreteGray};
|
||||
`
|
||||
|
||||
const TokenModalInfo = styled.div`
|
||||
@ -177,9 +201,8 @@ const TokenList = styled.div`
|
||||
const TokenModalRow = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
align-items: center;
|
||||
padding: 1rem 1.5rem;
|
||||
margin: 0.25rem 0.5rem;
|
||||
justify-content: space-between;
|
||||
padding: 0.8rem 2rem;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
@ -188,16 +211,55 @@ const TokenModalRow = styled.div`
|
||||
}
|
||||
|
||||
:hover {
|
||||
background-color: ${({ theme }) => theme.concreteGray};
|
||||
background-color: ${({ theme }) => theme.tokenRowHover};
|
||||
}
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`padding: 0.8rem 1rem;`}
|
||||
`
|
||||
|
||||
const TokenRowLeft = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
align-items : center;
|
||||
`
|
||||
|
||||
const TokenSymbolGroup = styled.div`
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
margin-left: 1rem;
|
||||
`
|
||||
|
||||
const TokenFullName = styled.div`
|
||||
color: ${({ theme }) => theme.chaliceGray};
|
||||
`
|
||||
|
||||
const TokenRowBalance = styled.div`
|
||||
font-size: 1rem;
|
||||
line-height: 20px;
|
||||
`
|
||||
|
||||
const TokenRowUsd = styled.div`
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
color: ${({ theme }) => theme.chaliceGray};
|
||||
`
|
||||
|
||||
const TokenRowRight = styled.div`
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
align-items: flex-end;
|
||||
`
|
||||
|
||||
const StyledTokenName = styled.span`
|
||||
margin: 0 0.25rem 0 0.25rem;
|
||||
`
|
||||
|
||||
const SpinnerWrapper = styled(Spinner)`
|
||||
margin: 0 0.25rem 0 0.25rem;
|
||||
color: ${({ theme }) => theme.chaliceGray};
|
||||
opacity: 0.6;
|
||||
`
|
||||
|
||||
export default function CurrencyInputPanel({
|
||||
onValueChange = () => {},
|
||||
allBalances,
|
||||
renderInput,
|
||||
onCurrencySelected = () => {},
|
||||
title,
|
||||
@ -236,7 +298,6 @@ export default function CurrencyInputPanel({
|
||||
selectedTokenExchangeAddress,
|
||||
ethers.constants.MaxUint256
|
||||
)
|
||||
|
||||
tokenContract
|
||||
.approve(selectedTokenExchangeAddress, ethers.constants.MaxUint256, {
|
||||
gasLimit: calculateGasMargin(estimatedGas, GAS_MARGIN)
|
||||
@ -337,48 +398,106 @@ export default function CurrencyInputPanel({
|
||||
{!disableTokenSelect && (
|
||||
<CurrencySelectModal
|
||||
isOpen={modalIsOpen}
|
||||
// isOpen={true}
|
||||
onDismiss={() => {
|
||||
setModalIsOpen(false)
|
||||
}}
|
||||
onTokenSelect={onCurrencySelected}
|
||||
allBalances={allBalances}
|
||||
/>
|
||||
)}
|
||||
</InputPanel>
|
||||
)
|
||||
}
|
||||
|
||||
function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect }) {
|
||||
function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect, allBalances }) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const { exchangeAddress } = useTokenDetails(searchQuery)
|
||||
|
||||
const allTokens = useAllTokenDetails()
|
||||
|
||||
// BigNumber.js instance
|
||||
const ethPrice = useUSDPrice()
|
||||
|
||||
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())
|
||||
const usdBalance = balanceBigNumber.times(USDRate).div(new BigNumber(10).pow(allTokens[k].decimals))
|
||||
return usdBalance
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})
|
||||
const usdAmounts =
|
||||
_usdAmounts &&
|
||||
Object.keys(allTokens).reduce(
|
||||
(accumulator, currentValue, i) => Object.assign({ [currentValue]: _usdAmounts[i] }, accumulator),
|
||||
{}
|
||||
)
|
||||
|
||||
const tokenList = useMemo(() => {
|
||||
return Object.keys(allTokens)
|
||||
.sort((a, b) => {
|
||||
const aSymbol = allTokens[a].symbol.toLowerCase()
|
||||
const bSymbol = allTokens[b].symbol.toLowerCase()
|
||||
|
||||
if (aSymbol === 'ETH'.toLowerCase() || bSymbol === 'ETH'.toLowerCase()) {
|
||||
return aSymbol === bSymbol ? 0 : aSymbol === 'ETH'.toLowerCase() ? -1 : 1
|
||||
} else {
|
||||
return aSymbol < bSymbol ? -1 : aSymbol > bSymbol ? 1 : 0
|
||||
}
|
||||
|
||||
if (usdAmounts[a] && !usdAmounts[b]) {
|
||||
return -1
|
||||
} else if (usdAmounts[b] && !usdAmounts[a]) {
|
||||
return 1
|
||||
}
|
||||
|
||||
// check for balance - sort by value
|
||||
if (usdAmounts[a] && usdAmounts[b]) {
|
||||
const aUSD = usdAmounts[a]
|
||||
const bUSD = usdAmounts[b]
|
||||
|
||||
return aUSD.gt(bUSD) ? -1 : aUSD.lt(bUSD) ? 1 : 0
|
||||
}
|
||||
|
||||
return aSymbol < bSymbol ? -1 : aSymbol > bSymbol ? 1 : 0
|
||||
})
|
||||
.map(k => {
|
||||
let balance
|
||||
let usdBalance
|
||||
// only update if we have data
|
||||
if (k === 'ETH' && allBalances && allBalances[k]) {
|
||||
balance = formatEthBalance(allBalances[k].balance)
|
||||
usdBalance = usdAmounts[k]
|
||||
} else if (allBalances && allBalances[k]) {
|
||||
balance = formatTokenBalance(allBalances[k].balance, allTokens[k].decimals)
|
||||
usdBalance = usdAmounts[k]
|
||||
}
|
||||
return {
|
||||
name: allTokens[k].name,
|
||||
symbol: allTokens[k].symbol,
|
||||
address: k
|
||||
address: k,
|
||||
balance: balance,
|
||||
usdBalance: usdBalance
|
||||
}
|
||||
})
|
||||
}, [allTokens])
|
||||
}, [allBalances, allTokens, usdAmounts])
|
||||
|
||||
const filteredTokenList = useMemo(() => {
|
||||
return tokenList.filter(tokenEntry => {
|
||||
// check the regex for each field
|
||||
const regexMatches = Object.keys(tokenEntry).map(tokenEntryKey => {
|
||||
return (
|
||||
tokenEntry[tokenEntryKey] &&
|
||||
typeof tokenEntry[tokenEntryKey] === 'string' &&
|
||||
!!tokenEntry[tokenEntryKey].match(new RegExp(escapeStringRegex(searchQuery), 'i'))
|
||||
)
|
||||
})
|
||||
@ -397,7 +516,6 @@ function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect }) {
|
||||
if (isAddress(searchQuery) && exchangeAddress === undefined) {
|
||||
return <TokenModalInfo>Searching for Exchange...</TokenModalInfo>
|
||||
}
|
||||
|
||||
if (isAddress(searchQuery) && exchangeAddress === ethers.constants.AddressZero) {
|
||||
return (
|
||||
<>
|
||||
@ -408,16 +526,30 @@ function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect }) {
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
if (!filteredTokenList.length) {
|
||||
return <TokenModalInfo>{t('noExchange')}</TokenModalInfo>
|
||||
}
|
||||
|
||||
return filteredTokenList.map(({ address, symbol }) => {
|
||||
return filteredTokenList.map(({ address, symbol, name, balance, usdBalance }) => {
|
||||
return (
|
||||
<TokenModalRow key={address} onClick={() => _onTokenSelect(address)}>
|
||||
<TokenLogo address={address} />
|
||||
<span id="symbol">{symbol}</span>
|
||||
<TokenRowLeft>
|
||||
<TokenLogo address={address} size={'2rem'} />
|
||||
<TokenSymbolGroup>
|
||||
<span id="symbol">{symbol}</span>
|
||||
<TokenFullName>{name}</TokenFullName>
|
||||
</TokenSymbolGroup>
|
||||
</TokenRowLeft>
|
||||
<TokenRowRight>
|
||||
{balance ? (
|
||||
<TokenRowBalance>{balance && (balance > 0 || balance === '<0.0001') ? balance : '-'}</TokenRowBalance>
|
||||
) : (
|
||||
<SpinnerWrapper src={Circle} alt="loader" />
|
||||
)}
|
||||
<TokenRowUsd>
|
||||
{usdBalance ? (usdBalance.lt(0.01) ? '<$0.01' : '$' + formatToUsd(usdBalance)) : ''}
|
||||
</TokenRowUsd>
|
||||
</TokenRowRight>
|
||||
</TokenModalRow>
|
||||
)
|
||||
})
|
||||
@ -432,12 +564,33 @@ function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect }) {
|
||||
setSearchQuery(checksummedInput || input)
|
||||
}
|
||||
|
||||
function clearInputAndDismiss() {
|
||||
setSearchQuery('')
|
||||
onDismiss()
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onDismiss={onDismiss} minHeight={50} initialFocusRef={isMobile ? undefined : inputRef}>
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onDismiss={clearInputAndDismiss}
|
||||
minHeight={60}
|
||||
initialFocusRef={isMobile ? undefined : inputRef}
|
||||
>
|
||||
<TokenModal>
|
||||
<ModalHeader>
|
||||
<p>Select Token</p>
|
||||
<CloseIcon onClick={clearInputAndDismiss}>
|
||||
<img src={close} alt={'close icon'} />
|
||||
</CloseIcon>
|
||||
</ModalHeader>
|
||||
<SearchContainer>
|
||||
<StyledBorderlessInput ref={inputRef} type="text" placeholder={t('searchOrPaste')} onChange={onInput} />
|
||||
<img src={SearchIcon} alt="search" />
|
||||
<StyledBorderlessInput
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
placeholder={isMobile ? t('searchOrPasteMobile') : t('searchOrPaste')}
|
||||
onChange={onInput}
|
||||
/>
|
||||
</SearchContainer>
|
||||
<TokenList>{renderTokenList()}</TokenList>
|
||||
</TokenModal>
|
||||
|
@ -1,7 +1,9 @@
|
||||
import React, { useState, useReducer, useEffect } from 'react'
|
||||
import ReactGA from 'react-ga'
|
||||
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useWeb3Context } from 'web3-react'
|
||||
|
||||
import { ethers } from 'ethers'
|
||||
import styled from 'styled-components'
|
||||
|
||||
@ -16,6 +18,7 @@ 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'
|
||||
|
||||
const INPUT = 0
|
||||
@ -251,6 +254,7 @@ export default function ExchangePage({ initialCurrency, sending }) {
|
||||
|
||||
// core swap state
|
||||
const [swapState, dispatchSwapState] = useReducer(swapStateReducer, initialCurrency, getInitialSwapState)
|
||||
|
||||
const { independentValue, dependentValue, independentField, inputCurrency, outputCurrency } = swapState
|
||||
|
||||
const [recipient, setRecipient] = useState({ address: '', name: '' })
|
||||
@ -583,10 +587,13 @@ export default function ExchangePage({ initialCurrency, sending }) {
|
||||
|
||||
const [customSlippageError, setcustomSlippageError] = useState('')
|
||||
|
||||
const allBalances = useFetchAllBalances()
|
||||
|
||||
return (
|
||||
<>
|
||||
<CurrencyInputPanel
|
||||
title={t('input')}
|
||||
allBalances={allBalances}
|
||||
description={inputValueFormatted && independentField === OUTPUT ? estimatedText : ''}
|
||||
extraText={inputBalanceFormatted && formatBalance(inputBalanceFormatted)}
|
||||
extraTextClickHander={() => {
|
||||
@ -626,6 +633,7 @@ export default function ExchangePage({ initialCurrency, sending }) {
|
||||
</OversizedPanel>
|
||||
<CurrencyInputPanel
|
||||
title={t('output')}
|
||||
allBalances={allBalances}
|
||||
description={outputValueFormatted && independentField === INPUT ? estimatedText : ''}
|
||||
extraText={outputBalanceFormatted && formatBalance(outputBalanceFormatted)}
|
||||
onCurrencySelected={outputCurrency => {
|
||||
|
@ -28,7 +28,7 @@ const StyledDialogContent = styled(FilteredDialogContent)`
|
||||
width: 50vw;
|
||||
max-width: 650px;
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`width: 65vw;`}
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`width: 80vw;`}
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`width: 85vw;`}
|
||||
max-height: 50vh;
|
||||
${({ minHeight }) =>
|
||||
minHeight &&
|
||||
@ -39,7 +39,7 @@ const StyledDialogContent = styled(FilteredDialogContent)`
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`max-height: 80vh;`}
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
border-radius: 1.5rem;
|
||||
border-radius: 10px;
|
||||
}
|
||||
`
|
||||
|
||||
|
@ -10,12 +10,13 @@ const BAD_IMAGES = {}
|
||||
const Image = styled.img`
|
||||
width: ${({ size }) => size};
|
||||
height: ${({ size }) => size};
|
||||
background-color: white;
|
||||
border-radius: 1rem;
|
||||
`
|
||||
|
||||
const Emoji = styled.span`
|
||||
width: ${({ size }) => size};
|
||||
font-size: ${({ size }) => size};
|
||||
height: ${({ size }) => size};
|
||||
`
|
||||
|
||||
const StyledEthereumLogo = styled(EthereumLogo)`
|
||||
@ -33,7 +34,7 @@ export default function TokenLogo({ address, size = '1rem', ...rest }) {
|
||||
path = TOKEN_ICON_API(address.toLowerCase())
|
||||
} else {
|
||||
return (
|
||||
<Emoji {...rest}>
|
||||
<Emoji {...rest} size={size}>
|
||||
<span role="img" aria-label="Thinking">
|
||||
🤔
|
||||
</span>
|
||||
|
@ -580,7 +580,6 @@ export default function TransactionDetails(props) {
|
||||
)} ${props.inputSymbol}`
|
||||
)}
|
||||
</ValueWrapper>
|
||||
.
|
||||
</div>
|
||||
<LastSummaryText>
|
||||
{b(props.recipientAddress)} {t('willReceive')}{' '}
|
||||
@ -595,7 +594,7 @@ export default function TransactionDetails(props) {
|
||||
</ValueWrapper>{' '}
|
||||
</LastSummaryText>
|
||||
<LastSummaryText>
|
||||
{t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>.
|
||||
{t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>
|
||||
</LastSummaryText>
|
||||
</TransactionInfo>
|
||||
) : (
|
||||
@ -623,7 +622,7 @@ export default function TransactionDetails(props) {
|
||||
</ValueWrapper>
|
||||
</div>
|
||||
<LastSummaryText>
|
||||
{t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>.
|
||||
{t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>
|
||||
</LastSummaryText>
|
||||
</TransactionInfo>
|
||||
)
|
||||
@ -641,10 +640,7 @@ export default function TransactionDetails(props) {
|
||||
)} ${props.outputSymbol}`
|
||||
)}
|
||||
</ValueWrapper>{' '}
|
||||
{t('to')} {b(props.recipientAddress)}
|
||||
</div>
|
||||
<LastSummaryText>
|
||||
{t('itWillCost')}{' '}
|
||||
{t('to')} {b(props.recipientAddress)} {t('forAtMost')}{' '}
|
||||
<ValueWrapper>
|
||||
{b(
|
||||
`${amountFormatter(
|
||||
@ -654,39 +650,35 @@ export default function TransactionDetails(props) {
|
||||
)} ${props.inputSymbol}`
|
||||
)}
|
||||
</ValueWrapper>{' '}
|
||||
</LastSummaryText>
|
||||
</div>
|
||||
<LastSummaryText>
|
||||
{t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>.
|
||||
{t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>
|
||||
</LastSummaryText>
|
||||
</TransactionInfo>
|
||||
) : (
|
||||
<TransactionInfo>
|
||||
<div>
|
||||
{t('youAreBuying')}{' '}
|
||||
<ValueWrapper>
|
||||
{b(
|
||||
`${amountFormatter(
|
||||
props.independentValueParsed,
|
||||
props.independentDecimals,
|
||||
Math.min(4, props.independentDecimals)
|
||||
)} ${props.outputSymbol}`
|
||||
)}
|
||||
</ValueWrapper>
|
||||
</div>
|
||||
{t('youAreBuying')}{' '}
|
||||
<ValueWrapper>
|
||||
{b(
|
||||
`${amountFormatter(
|
||||
props.independentValueParsed,
|
||||
props.independentDecimals,
|
||||
Math.min(4, props.independentDecimals)
|
||||
)} ${props.outputSymbol}`
|
||||
)}
|
||||
</ValueWrapper>{' '}
|
||||
{t('forAtMost')}{' '}
|
||||
<ValueWrapper>
|
||||
{b(
|
||||
`${amountFormatter(
|
||||
props.dependentValueMaximum,
|
||||
props.dependentDecimals,
|
||||
Math.min(4, props.dependentDecimals)
|
||||
)} ${props.inputSymbol}`
|
||||
)}
|
||||
</ValueWrapper>{' '}
|
||||
<LastSummaryText>
|
||||
{t('itWillCost')}{' '}
|
||||
<ValueWrapper>
|
||||
{b(
|
||||
`${amountFormatter(
|
||||
props.dependentValueMaximum,
|
||||
props.dependentDecimals,
|
||||
Math.min(4, props.dependentDecimals)
|
||||
)} ${props.inputSymbol}`
|
||||
)}
|
||||
</ValueWrapper>{' '}
|
||||
</LastSummaryText>
|
||||
<LastSummaryText>
|
||||
{t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>.
|
||||
{t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>
|
||||
</LastSummaryText>
|
||||
</TransactionInfo>
|
||||
)
|
||||
|
@ -59,9 +59,13 @@ const Web3StatusConnected = styled(Web3StatusGeneric)`
|
||||
color: ${({ pending, theme }) => (pending ? theme.royalBlue : theme.doveGray)};
|
||||
font-weight: 400;
|
||||
:hover {
|
||||
|
||||
> P {
|
||||
color: ${({ theme }) => theme.uniswapPink};
|
||||
}
|
||||
background-color: ${({ pending, theme }) =>
|
||||
pending ? transparentize(0.9, theme.royalBlue) : transparentize(0.9, theme.mercuryGray)};
|
||||
}
|
||||
|
||||
:focus {
|
||||
border: 1px solid
|
||||
${({ pending, theme }) => (pending ? darken(0.1, theme.royalBlue) : darken(0.1, theme.mercuryGray))};
|
||||
|
102
src/contexts/AllBalances.js
Normal file
102
src/contexts/AllBalances.js
Normal file
@ -0,0 +1,102 @@
|
||||
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
|
||||
}
|
@ -1,10 +1,14 @@
|
||||
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
|
||||
import { useWeb3Context } from 'web3-react'
|
||||
import { safeAccess } from '../utils'
|
||||
|
||||
const BLOCK_NUMBERS = 'BLOCK_NUMBERS'
|
||||
import { safeAccess } from '../utils'
|
||||
import { getUSDPrice } from '../utils/price'
|
||||
|
||||
const BLOCK_NUMBER = 'BLOCK_NUMBER'
|
||||
const USD_PRICE = 'USD_PRICE'
|
||||
|
||||
const UPDATE_BLOCK_NUMBER = 'UPDATE_BLOCK_NUMBER'
|
||||
const UPDATE_USD_PRICE = 'UPDATE_USD_PRICE'
|
||||
|
||||
const ApplicationContext = createContext()
|
||||
|
||||
@ -18,12 +22,22 @@ function reducer(state, { type, payload }) {
|
||||
const { networkId, blockNumber } = payload
|
||||
return {
|
||||
...state,
|
||||
[BLOCK_NUMBERS]: {
|
||||
...(safeAccess(state, [BLOCK_NUMBERS]) || {}),
|
||||
[BLOCK_NUMBER]: {
|
||||
...(safeAccess(state, [BLOCK_NUMBER]) || {}),
|
||||
[networkId]: blockNumber
|
||||
}
|
||||
}
|
||||
}
|
||||
case UPDATE_USD_PRICE: {
|
||||
const { networkId, USDPrice } = payload
|
||||
return {
|
||||
...state,
|
||||
[USD_PRICE]: {
|
||||
...(safeAccess(state, [USD_PRICE]) || {}),
|
||||
[networkId]: USDPrice
|
||||
}
|
||||
}
|
||||
}
|
||||
default: {
|
||||
throw Error(`Unexpected action type in ApplicationContext reducer: '${type}'.`)
|
||||
}
|
||||
@ -32,15 +46,22 @@ function reducer(state, { type, payload }) {
|
||||
|
||||
export default function Provider({ children }) {
|
||||
const [state, dispatch] = useReducer(reducer, {
|
||||
[BLOCK_NUMBERS]: {}
|
||||
[BLOCK_NUMBER]: {},
|
||||
[USD_PRICE]: {}
|
||||
})
|
||||
|
||||
const updateBlockNumber = useCallback((networkId, blockNumber) => {
|
||||
dispatch({ type: UPDATE_BLOCK_NUMBER, payload: { networkId, blockNumber } })
|
||||
}, [])
|
||||
|
||||
const updateUSDPrice = useCallback((networkId, USDPrice) => {
|
||||
dispatch({ type: UPDATE_USD_PRICE, payload: { networkId, USDPrice } })
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<ApplicationContext.Provider value={useMemo(() => [state, { updateBlockNumber }], [state, updateBlockNumber])}>
|
||||
<ApplicationContext.Provider
|
||||
value={useMemo(() => [state, { updateBlockNumber, updateUSDPrice }], [state, updateBlockNumber, updateUSDPrice])}
|
||||
>
|
||||
{children}
|
||||
</ApplicationContext.Provider>
|
||||
)
|
||||
@ -49,7 +70,24 @@ export default function Provider({ children }) {
|
||||
export function Updater() {
|
||||
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(() => {
|
||||
if ((networkId || networkId === 0) && library) {
|
||||
@ -88,5 +126,13 @@ export function useBlockNumber() {
|
||||
|
||||
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) {
|
||||
update(networkId, address, tokenAddress, null, globalBlockNumber)
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
stale = true
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ const LAST_SAVED = 'LAST_SAVED'
|
||||
|
||||
const BETA_MESSAGE_DISMISSED = 'BETA_MESSAGE_DISMISSED'
|
||||
const DARK_MODE = 'DARK_MODE'
|
||||
|
||||
const UPDATABLE_KEYS = [BETA_MESSAGE_DISMISSED, DARK_MODE]
|
||||
|
||||
const UPDATE_KEY = 'UPDATE_KEY'
|
||||
|
@ -466,7 +466,6 @@ export function useTokenDetails(tokenAddress) {
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return () => {
|
||||
stale = true
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import TransactionContextProvider, { Updater as TransactionContextUpdater } from
|
||||
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 InjectedConnector from './InjectedConnector'
|
||||
@ -35,7 +36,9 @@ function ContextProviders({ children }) {
|
||||
<TransactionContextProvider>
|
||||
<TokensContextProvider>
|
||||
<BalancesContextProvider>
|
||||
<AllowancesContextProvider>{children}</AllowancesContextProvider>
|
||||
<AllBalancesContextProvider>
|
||||
<AllowancesContextProvider>{children}</AllowancesContextProvider>
|
||||
</AllBalancesContextProvider>
|
||||
</BalancesContextProvider>
|
||||
</TokensContextProvider>
|
||||
</TransactionContextProvider>
|
||||
|
@ -15,6 +15,7 @@ import { useExchangeContract } from '../../hooks'
|
||||
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'
|
||||
|
||||
@ -534,6 +535,8 @@ export default function AddLiquidity() {
|
||||
const isActive = active && account
|
||||
const isValid = (inputError === null || outputError === null) && !showUnlock
|
||||
|
||||
const allBalances = useFetchAllBalances()
|
||||
|
||||
return (
|
||||
<>
|
||||
{isNewExchange ? (
|
||||
@ -550,6 +553,7 @@ export default function AddLiquidity() {
|
||||
|
||||
<CurrencyInputPanel
|
||||
title={t('deposit')}
|
||||
allBalances={allBalances}
|
||||
extraText={inputBalance && formatBalance(amountFormatter(inputBalance, 18, 4))}
|
||||
onValueChange={inputValue => {
|
||||
dispatchAddLiquidityState({ type: 'UPDATE_VALUE', payload: { value: inputValue, field: INPUT } })
|
||||
@ -566,6 +570,7 @@ export default function AddLiquidity() {
|
||||
</OversizedPanel>
|
||||
<CurrencyInputPanel
|
||||
title={t('deposit')}
|
||||
allBalances={allBalances}
|
||||
description={isNewExchange ? '' : outputValue ? `(${t('estimated')})` : ''}
|
||||
extraText={outputBalance && formatBalance(amountFormatter(outputBalance, decimals, Math.min(decimals, 4)))}
|
||||
selectedTokenAddress={outputCurrency}
|
||||
|
@ -15,6 +15,7 @@ import { useExchangeContract } from '../../hooks'
|
||||
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
|
||||
@ -328,10 +329,13 @@ export default function RemoveLiquidity() {
|
||||
|
||||
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) {
|
||||
@ -354,6 +358,7 @@ export default function RemoveLiquidity() {
|
||||
</OversizedPanel>
|
||||
<CurrencyInputPanel
|
||||
title={t('output')}
|
||||
allBalances={allBalances}
|
||||
description={!!(ethWithdrawn && tokenWithdrawn) ? `(${t('estimated')})` : ''}
|
||||
key="remove-liquidity-input"
|
||||
renderInput={() =>
|
||||
|
@ -69,7 +69,7 @@ export const BorderlessInput = styled.input`
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
color: ${({ theme }) => theme.placeholderGray};
|
||||
color: ${({ theme }) => theme.chaliceGray};
|
||||
}
|
||||
`
|
||||
|
||||
|
@ -51,12 +51,14 @@ const theme = darkMode => ({
|
||||
doveGray: darkMode ? '#C4C4C4' : '#737373',
|
||||
mineshaftGray: darkMode ? '#E1E1E1' : '#2B2B2B',
|
||||
buttonOutlineGrey: darkMode ? '#FAFAFA' : '#F2F2F2',
|
||||
tokenRowHover: darkMode ? '#404040' : '#F2F2F2',
|
||||
//blacks
|
||||
charcoalBlack: darkMode ? '#F2F2F2' : '#404040',
|
||||
// blues
|
||||
zumthorBlue: darkMode ? '#212529' : '#EBF4FF',
|
||||
malibuBlue: darkMode ? '#E67AEF' : '#5CA2FF',
|
||||
royalBlue: darkMode ? '#DC6BE5' : '#2F80ED',
|
||||
loadingBlue: darkMode ? '#e4f0ff' : '#e4f0ff',
|
||||
// purples
|
||||
wisteriaPurple: '#DC6BE5',
|
||||
// reds
|
||||
@ -69,6 +71,9 @@ const theme = darkMode => ({
|
||||
uniswapPink: '#DC6BE5',
|
||||
connectedGreen: '#27AE60',
|
||||
|
||||
//specific
|
||||
textHover: darkMode ? theme.uniswapPink : theme.doveGray,
|
||||
|
||||
// media queries
|
||||
mediaWidth: mediaWidthTemplates,
|
||||
// css snippets
|
||||
|
@ -5,6 +5,7 @@ import EXCHANGE_ABI from '../constants/abis/exchange'
|
||||
import ERC20_ABI from '../constants/abis/erc20'
|
||||
import ERC20_BYTES32_ABI from '../constants/abis/erc20_bytes32'
|
||||
import { FACTORY_ADDRESSES } from '../constants'
|
||||
import { formatFixed } from '@uniswap/sdk'
|
||||
|
||||
import UncheckedJsonRpcSigner from './signer'
|
||||
|
||||
@ -178,10 +179,27 @@ export async function getEtherBalance(address, library) {
|
||||
if (!isAddress(address)) {
|
||||
throw Error(`Invalid 'address' parameter '${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
|
||||
export async function getTokenBalance(tokenAddress, address, library) {
|
||||
if (!isAddress(tokenAddress) || !isAddress(address)) {
|
||||
|
49
src/utils/math.js
Normal file
49
src/utils/math.js
Normal file
@ -0,0 +1,49 @@
|
||||
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))]
|
||||
}
|
43
src/utils/price.js
Normal file
43
src/utils/price.js
Normal file
@ -0,0 +1,43 @@
|
||||
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]
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue
Block a user