stable version with updated balances, add liquidity using SDK, pair menu search

This commit is contained in:
ianlapham 2020-02-26 12:00:59 -05:00
parent 3091ebc158
commit 753e5f3423
45 changed files with 3293 additions and 2657 deletions

@ -5,9 +5,14 @@
"homepage": "https://uniswap.exchange",
"private": true,
"dependencies": {
"@ethersproject/units": "^5.0.0-beta.132",
"@reach/dialog": "^0.2.8",
"@reach/tooltip": "^0.2.0",
"@uniswap/sdk": "^1.0.0-beta.4",
"@types/jest": "^25.1.3",
"@types/node": "^13.7.4",
"@types/react": "^16.9.21",
"@types/react-dom": "^16.9.5",
"@uniswap/sdk": "next",
"@web3-react/core": "^6.0.2",
"@web3-react/fortmatic-connector": "^6.0.2",
"@web3-react/injected-connector": "^6.0.3",
@ -36,7 +41,9 @@
"react-spring": "^8.0.27",
"react-switch": "^5.0.1",
"react-use-gesture": "^6.0.14",
"styled-components": "^4.2.0"
"rebass": "^4.0.7",
"styled-components": "^4.2.0",
"typescript": "^3.7.5"
},
"scripts": {
"start": "react-scripts start",

@ -9,7 +9,7 @@
"swapAnyway": "Swap Anyway",
"send": "Send",
"sendAnyway": "Send Anyway",
"pool": "Pool",
"pool": "Supply",
"betaWarning": "This project is in beta. Use at your own risk.",
"input": "Input",
"output": "Output",

@ -0,0 +1,3 @@
<svg width="94" height="94" viewBox="0 0 94 94" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M92 47C92 22.1472 71.8528 2 47 2C22.1472 2 2 22.1472 2 47C2 71.8528 22.1472 92 47 92" stroke="#2172E5" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 283 B

@ -1,11 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="256px" height="417px" viewBox="0 0 256 417" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<g>
<polygon fill="#343434" points="127.9611 0 125.1661 9.5 125.1661 285.168 127.9611 287.958 255.9231 212.32"/>
<polygon fill="#8C8C8C" points="127.962 0 0 212.32 127.962 287.959 127.962 154.158"/>
<polygon fill="#3C3C3B" points="127.9611 312.1866 126.3861 314.1066 126.3861 412.3056 127.9611 416.9066 255.9991 236.5866"/>
<polygon fill="#8C8C8C" points="127.962 416.9052 127.962 312.1852 0 236.5852"/>
<polygon fill="#141414" points="127.9611 287.9577 255.9211 212.3207 127.9611 154.1587"/>
<polygon fill="#393939" points="0.0009 212.3208 127.9609 287.9578 127.9609 154.1588"/>
</g>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect width="20" height="20" fill="url(#pattern0)"/>
<defs>
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0" transform="scale(0.0078125)"/>
</pattern>
<image id="image0" width="128" height="128" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAAAXNSR0IArs4c6QAADd9JREFUeNrtXdtzE+cV38kweWpm+tI8JZP8EZ0pL+UhferkfyAzFqEpadq0hbRNUlsYQ2ycBEgCgQQIgVxMbC6BAHbEJZg7dW4OpeCWwDTNDOyuLFuybMsXfT2/tWVkWavd1e5K58g6M9+Q8cTS+ju//b5z/R1Nq0F5Ppr46cpm8xcroubyFU3m+oYmY39Do97d0KRfoH/76d/b9K9JP89Yy/pv62f9s/9PN34Hv4vPwGfhM7W68JNVUfWTp9fqv440Ge2RRqOXFHiPFKhCWfTZ+A58F74T313XQIUlGlUP0lv5RCRqtkQa9YukjMnQFO6w8N3WM9Cz4JnwbHUNhSQrovGlkSZ9K216vFoKdwGIOJ4Rz1rXWADyTDTxeCSqv0wbe5Or0kuA4SaeHX9DXZMepSFq/Jw28FBDo5GVpviFy8hafwv9TXXNOh3zzfov6Qjtka90u1NB78HfWNd0gUTWmr+yLPgaVfwCIMCToL+5/sZH44+Qa9W5WBRfxK3sxB4sRlduSSRqrKYjMbVolX//WkhhL7Ani+O4j+rL6Ai8ttgVX+RauIa9qfG3Xm+tDcs+RI+B9qjmToNVLfFHKWJ2vq5gt6eBfr5mbIPIWuNJK9lSV6zXZWLvJB/5D1AApK1+5PsOIrVhL8UlbMiy7agrMKArgfZSTKKpoc14iHLpMUkb/OZHQ6pxa5x5zID2lPaWefIm9TC5M32SlP/MOl0Zg1PqWG9aUY6fu6vY99yG1M94GnsticfozhqQdryeOJ9W09NZ9cU/Rq2TQIBdMIC9ZvfmS1T+39+Kq6mp7BwATl8dVX/caIoAAfaczZ0v7djPrYE7EwqSAwDW/u6UlMhhX9VtAlim0gy+3Np9KKlykg8ArPXvDApJJhmxqnkHlp8v1NV77hVDJUembQHQfSFtGYeCXMTKxwlmgjwy/erevlGVL4UAwHqna1hS+VlbFcK7MiN8698dVNls1hEAWC+8bsqJGFYqbIzEjtTY/oqorn64O6kKxQ4AB0+NIF8vJncA3YSf0hWc1YOFX0zsAIDVvichKosYaip5Jp8vU/l/ftVUY+NZzwCIXRpVz24wBOUN9NbQKnkkZ/a+vD6u7KQUALDeP5KUVlSyLISjX24Z16Z9Q6qUOAEA6+U344KuAuNaoFcBoWqNVOX/pllXOiV7/ALg6NkR9XRUVC3BmsBKtyVX70JxTuIGAFhvfDgkKDagpwIpKyPLskuq8l98I64mJ7OBAeD0lVH1fJshySvo8t2xI7mS5vr3GeVG3AIA6+MTKVF74KsDSXK7FkK5bsULALCatw9KMgh7y27UlKp8+O1DqenQAIAiEhiXYiKg5TSkSu7SxV3tRbwCAGt7p6Rkkd7jze2jXnapysfxDIWGDQCs1a+ZcvbGCz/BDDmDyEZLdefHCeVVygXAgZicZBF06rK+L/G41JDvh8eSqhwpFwBYbbsTYkLEruhqwGsjUfko5hwdm644AGKX0mrVehmxAejWTdh3QCIArvSPqXLFDwCwdh9OSrkGbjqFfZdKVP6rlLP3I34BgPXiFhnJopIUduC4k6b8ldTNc9ecrDoAjpwZsSqOBBjKW+0bOhmTMNqtw6dHlF8JAgBYSDsLuAbiRUvJQXUqTfl/3WyqiYksGwCcogDU71sNAdeA+UQR699skQaA7/6dUUFIUADAgivK3xswW4qlfS9KUv62/cMqKAkSAFjRbbyTRdD1/FJvoj2vJuu217WqxVCDw1NsAXD8XNoyThnbAZPzqO7BfS/p7f/8YloFKUEDAGtrB2+DEDrPj/23S1F+07a452RPNQBwhlrNUYrO+BRoF1n4ceuHiUCVjzax67cy6pOeVOAg6Pw8xdgOyCsUCXXMSoAL9flBSYbcR/QKwHWDsta+PahadyXU3qNJq54gKBBs2MnUICSdW8rHMCQJyod/PTI67VvxQ8lpdfGbsQVKBgByC/wAOw8Mqx6yNfwCAPbKb1t47qk1CAsTsSQA4MLXY74U/6M+qc5SW/gZG0XlAyC31lFxyVtkzKG03A8Idh7kWT0E3dP9bz7FXfk4msuVgTsZetud3+RiAMhfm/YlrDu9XBAgaskwIrgc+f8NrN0VSrDg7fUiE9QL8PUNut8vuz/CnQCQWxupAOSDz5IWoZQXACBnwa16CLrXZocqsgVAVyzlWvGgfbn07ZirN75cAMzZCUQ0sYuO9pgHO+G195lVD5HuWVf/riGGjvGMs89/15hUvV/a3+9hACC3mncMqm1kJxxzYSecujxq8ROxqhbmnAP45sZ4ScX/578ZdSYgl61cAOSvzWQnHIiVthP2HU3yygk0YF4uU/7eYjJJxI7f3oT/ng40aBMEAHKr/b2ElRW0sxMY8RL3a7NDk9nx95qJ+ckexACufDcWaJAmLADk1gayE3YfGlYnL80HKxteYtK9xpHwCdm0nNyLT6lzX83E1sNQfJgAmIsnUGBp+/4hdbz3PhCY8BKb2uz4dHb8vbf/NxG60isFgDmDkQJLW4hj4OCpFA9eYtI9OwAcOzcyF5+v5KoEAObZCXvIaPxgiAEAmF0BSKGCzq3WAfA3KiNn0GFssjQCc02ex3rTNQcAXHG/40I7N2sE9nMOA2+hYxIBFOkAQJ0g7nxm4WDLDbwggd17z6dJsQAA5zDLcTSke5wA3XL6AOIWf68UALxEJFVcawFmVze7ZJBT2hRH6Cs7E6rnAt9IICJ9bhpE/lBtxjErGcQsHbyRwqiXKaPnxL6BaCF6A05f5QOAKC14MU73fO5KA41dVesBmsz1aAlbzu1ogvWPmj3k0J1cJRhWHx1PVR0AuJ5WNjuWYltkk4hzcIgEWgUhHEvCYP3nqn+RE8Cb7vQ7OHaPfjFScQDArXvWBUEE3FqEuPFdqHHg4A1YJWFci0L/sokYP8bvF4HeuJ2xegKcBkKg6OLk5fABALfODWsorrL8knPQz3NpILWKQjmXhReSPaKBA/kBpw3EG7mLsnBhAQCFKk7E0Whfe/fAwmdYt2OQiwt4T0RjCEq4CyVNqWHc+06EDPC/u2KpwAAAo81pmhieCYwlsUsLvRSMqmPZGMK5NQxvkh6fsi31dlNnhzp/sHuWCwDYF25KuWAPgCnEjm5+JSN20XmtYdybQ3FsIkVsJ6gAdoofYPNhedsVlNi5dX9qd3brYAugAsiWZfwqvwlk85pDJbSHoya/lIAWHlY2TgynDiPU5TkBAEaoU4s3XFR0ATvFIl7fy6saeEF7uASCCLyFbujf0fq1y0Unzks0AubTvON6LnxLP3cCERbKvbpdRCM7GNLLLyCIkEIRg+M4lXbXH/g9VRS1OMwABqgQeUT/HlxMN6FZXDUHTrqLNyBczXHiWFGKGCkkUYikeWn9Rk8hgOMUVnbyKJDDf++wt4wko+pfZ5IoSTRxqA/wIpgXCHewHNoW+Pub93mvSeBKJ29LEyeJKBIWfbERsE4Cd9JLDB5eQDlVSchhcJ0wZksUKY0qFj53uRyB/yRGkFJzAJHR6yizLhGJHqcrhy1V7GxQ6KYUEMCd88MLhJxBfn0eijd2dA77yiqilV0sWbREuviv/jXuizgCXgVoYXLegB/lc+r7K5suXtrACIRo/XIGBsEShiCUU55AxMAIiSNjQNrghzpuMdDFux4ZI3Vo1Gdn01UDAJM+v9LLy9AoiWPjVkTL5xD0AwDkKLgPjvI8Nk7q4MgXCqqIwgYAcv4i6OHLGRwpdXSsl5GxfgHAprrHbeHHYhke7ZVTsBwAoORMwl74Gh49Wy/YKQ0ASOfei0+FBgBUH6+UMDuYdKf5FQodPkJGREoaCHA8T05lAwcAij/WCBgZC51Bd1oQEokaqyVeBU5VROUAgB3Xn23Uz1itBSWUPlxCxsQ1aQCwqohuZQIDwMcnUjL+btIVdKYFKRRHXiZxpjAyc2AQ9QsAq7pHxIhYIwtdaWEIfXCrxKsAJBN+AYD0s4yjX2/VwpKZq0A/LxEEpaqInADw9idCXD7STeBHf6Gsaok/ypFb0E8VUSkAHDrFt7qnkPAJutEqIZG1xpMS7QFUAWWKVBHZAQCnRtW5/Nze+6QTrZJC6cU2iVfB3iJVRHYAABOJDG/HaNMqLXTXPEC+ZodEEGBYlBMA9h5JSvH3O6ALrRqC8mLimYlJA0BhFVEhAPhX98xx/MRsS7wrJQ1txkMUeOiTBoK2vCqiQgCA7kVAsKcPe69xkGeiqYfJEBmQBgK0bBcCAJ1HAoy+Aey5xkkiLYnHpIEgV0WUA4CE6h7sMfZa4yhApbTrAH37YBxBdQ+neT52xz67N7+YTSDNMEQfH1i8uBt8bO58N96BVBeRsav3oCZJrDiBFSySFzFkFeGjPayanx9c2Fhe7oBDbL/i4d0wE0hSs4jVyupVLLFTwSthyUw9Qf1KcCjmaA09pVvVK4GqVSSWl1WijCu0Sh6OpwGhfY3EauMwqnexFzX91pcsOW/UuxbxXd8VWOm26GuBulcktqH5adfy3bFTkycCNTFK60r22qVbdqPmYhL0ss+QVNSCx2AFcw557s+vywxdzQxnkbxUs5W1o2d3TctSF0eDcSk47jiTWeLZ8IyOVGx18ZdoAtUp+G5BelxNlnN8t/UM9Cx4JnEJm5oIMxPtObjvMQDB8iTCHH9Dn21Z8PRd+M4FlOt14SEYhoSJWJFG8ylrLiINSIQFPkuJ3z87MNvE+HRrWf9t/ax/5lQhTwS/Q7+Lz8BnzQ1YqjH5P29N0rBVv2N5AAAAAElFTkSuQmCC"/>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 840 B

After

Width:  |  Height:  |  Size: 5.2 KiB

@ -0,0 +1,99 @@
import React from 'react'
import { Button as RebassButton } from 'rebass/styled-components'
import styled from 'styled-components'
import { darken } from 'polished'
import { RowBetween } from '../Row'
import { ChevronDown } from 'react-feather'
const Base = styled(RebassButton)`
padding: ${({ padding }) => (padding ? padding : '16px')};
width: ${({ width }) => (width ? width : '100%')};
font-size: 1rem;
font-weight: 500;
text-align: center;
border-radius: 20px;
outline: none;
border: 1px solid transparent;
color: white;
cursor: pointer;
&:disabled {
cursor: auto;
}
`
export const ButtonPrimary = styled(Base)`
background-color: ${({ theme }) => theme.royalBlue};
color: white;
&:focus {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.royalBlue)};
background-color: ${({ theme }) => darken(0.05, theme.royalBlue)};
}
&:hover {
background-color: ${({ theme }) => darken(0.05, theme.royalBlue)};
}
&:active {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, theme.royalBlue)};
background-color: ${({ theme }) => darken(0.1, theme.royalBlue)};
}
&:disabled {
background-color: ${({ theme }) => theme.royalBlue};
opacity: 50%;
cursor: auto;
}
`
export const ButtonSecondary = styled(Base)`
background-color: #ebf4ff;
color: #2172e5;
border-radius: 8px;
padding: 10px;
&:focus {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, '#ebf4ff')};
background-color: ${({ theme }) => darken(0.05, '#ebf4ff')};
}
&:hover {
background-color: ${({ theme }) => darken(0.05, '#ebf4ff')};
}
&:active {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, '#ebf4ff')};
background-color: ${({ theme }) => darken(0.1, '#ebf4ff')};
}
&:disabled {
background-color: ${({ theme }) => '#ebf4ff'};
opacity: 50%;
cursor: auto;
}
`
export const ButtonEmpty = styled(Base)`
border: 1px solid #edeef2;
background-color: transparent;
color: black;
&:focus {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, '#edeef2')};
}
&:hover {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, '#edeef2')};
}
&:active {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, '#edeef2')};
}
&:disabled {
opacity: 50%;
cursor: auto;
}
`
export function ButtonDropwdown({ disabled, children, ...rest }) {
return (
<ButtonPrimary {...rest}>
<RowBetween>
<div style={{ display: 'flex', alignItems: 'center' }}>{children}</div>
<ChevronDown size={24} />
</RowBetween>
</ButtonPrimary>
)
}

@ -0,0 +1,16 @@
import styled from 'styled-components'
import { Box } from 'rebass/styled-components'
const Card = styled(Box)`
width: 100%;
border-radius: 20px;
padding: 1rem;
padding: ${({ padding }) => padding};
border: ${({ border }) => border};
`
export default Card
export const LightCard = styled(Card)`
border: 1px solid ${({ theme }) => theme.outlineGrey};
`

@ -0,0 +1,20 @@
import styled from 'styled-components'
const Column = styled.div`
display: flex;
flex-direction: column;
justify-content: flex-start;
`
export const ColumnCenter = styled(Column)`
width: 100%;
align-items: center;
`
export const AutoColumn = styled.div`
display: grid;
grid-auto-rows: auto;
grid-row-gap: ${({ gap }) => gap};
justify-items: ${({ justify }) => justify && justify};
`
export default Column

@ -0,0 +1,217 @@
import React, { useState } from 'react'
import styled from 'styled-components'
import { ButtonPrimary } from '../../components/Button'
import { AutoColumn, ColumnCenter } from '../Column'
import Row, { RowBetween, RowFlat, RowFixed } from '../Row'
import { Text } from 'rebass'
import Modal from '../Modal'
import { CheckCircle } from 'react-feather'
import DoubleTokenLogo from '../DoubleLogo'
import TokenLogo from '../TokenLogo'
import { CloseIcon } from '../../theme/components'
import Loader from '../Loader'
const Wrapper = styled.div`
width: 100%;
`
const Section = styled(AutoColumn)`
padding: 2rem;
`
const BottomSection = styled(Section)`
background-color: ${({ theme }) => theme.activeGray};
`
const ConfirmedIcon = styled(ColumnCenter)`
padding: 60px 0;
`
export default function ConfirmationModal({
isOpen,
onDismiss,
liquidityMinted,
amount0,
amount1,
poolTokenPercentage,
price
}) {
const { address: address0, symbol: symbol0 } = amount0?.token || {}
const { address: address1, symbol: symbol1 } = amount1?.token || {}
const [confirmed, SetConfirmed] = useState(false)
const [waitingForConfirmation, setWaitingForConfirmation] = useState(false)
function WrappedOnDismissed() {
onDismiss()
SetConfirmed(false)
}
function fakeCall() {
setTimeout(() => {
setWaitingForConfirmation(false)
}, 2000)
}
return (
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={100}>
{!confirmed ? (
<Wrapper>
<Section gap="40px">
<RowBetween>
<Text fontWeight={500} fontSize={'20px'}>
You will receive
</Text>
<CloseIcon onClick={WrappedOnDismissed} />
</RowBetween>
<AutoColumn gap="16px">
<RowFlat>
<Text fontSize="48px" fontWeight={500} lineHeight="32px" marginRight={10}>
{liquidityMinted?.toFixed(6)}
</Text>
<DoubleTokenLogo a0={address0 || ''} a1={address1 || ''} size={20} />
</RowFlat>
<Row>
<Text fontSize="24px">{symbol0 + ':' + symbol1 + ' Pool Tokens'}</Text>
</Row>
</AutoColumn>
</Section>
<BottomSection gap="12px">
{/* <Text fontWeight={500} fontSize={16}>
Deposited Tokens
</Text> */}
{/* <LightCard>
<RowBetween>
<Text fontWeight={500} fontSize={20}>
{amountFormatter(amount0, decimals0, 4)}
</Text>
<RowFixed>
<TokenLogo address={token0 || ''} size={'24px'} />
<Text fontWeight={500} fontSize={20} marginLeft="12px">
{symbol0}
</Text>
</RowFixed>
</RowBetween>
</LightCard>
<ColumnCenter>
<Plus size="16" color="#888D9B" />
</ColumnCenter>
<LightCard>
<RowBetween>
<Text fontWeight={500} fontSize={20}>
{amountFormatter(amount1, decimals1, 4)}
</Text>
<RowFixed>
<TokenLogo address={token1 || ''} size={'24px'} />
<Text fontWeight={500} fontSize={16} marginLeft="12px">
{symbol1}
</Text>
</RowFixed>
</RowBetween>
</LightCard> */}
<AutoColumn gap="12px">
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
{symbol0} Deposited
</Text>
<RowFixed>
<TokenLogo address={address0 || ''} style={{ marginRight: '8px' }} />
<Text fontWeight={500} fontSize={16}>
{amount0?.toSignificant(6)}
</Text>
</RowFixed>
</RowBetween>
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
{symbol1} Deposited
</Text>
<RowFixed>
<TokenLogo address={address1 || ''} style={{ marginRight: '8px' }} />
<Text fontWeight={500} fontSize={16}>
{amount1?.toSignificant(6)}
</Text>
</RowFixed>
</RowBetween>
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
Rate
</Text>
<Text fontWeight={500} fontSize={16}>
{price && `1 ${symbol0} = ${price?.adjusted.toFixed(8)} ${symbol1}`}
</Text>
</RowBetween>
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
Minted Pool Share:
</Text>
<Text fontWeight={500} fontSize={16}>
{poolTokenPercentage?.toFixed(6) + '%'}
</Text>
</RowBetween>
<ButtonPrimary
style={{ margin: '20px 0' }}
onClick={() => {
setWaitingForConfirmation(true)
SetConfirmed(true)
fakeCall()
}}
>
<Text fontWeight={500} fontSize={20}>
Confirm Supply
</Text>
</ButtonPrimary>
<Text fontSize={12} color="#565A69" textAlign="center">
{`Output is estimated. You will receive at least ${liquidityMinted?.toFixed(
6
)} UNI ${symbol0}/${symbol1} or the transaction will revert.`}
</Text>
</AutoColumn>
</BottomSection>
</Wrapper>
) : (
<Wrapper>
<Section>
<RowBetween>
<div />
<CloseIcon onClick={WrappedOnDismissed} />
</RowBetween>
<ConfirmedIcon>
{waitingForConfirmation ? <Loader size="90px" /> : <CheckCircle size={90} color="#27AE60" />}
</ConfirmedIcon>
<AutoColumn gap="24px" justify={'center'}>
<Text fontWeight={500} fontSize={20}>
{!waitingForConfirmation ? 'Transaction Submitted' : 'Waiting For Confirmation'}
</Text>
<AutoColumn gap="12px" justify={'center'}>
<Text fontWeight={500} fontSize={16} color="#2172E5">
Supplied
</Text>
<Text fontWeight={600} fontSize={16} color="#2172E5">
{`${amount0?.toSignificant(6)} ${symbol0} and ${amount1?.toSignificant(6)} ${symbol1}`}
</Text>
</AutoColumn>
{!waitingForConfirmation && (
<>
<Text fontWeight={500} fontSize={14} color="#2172E5">
View on Etherscan
</Text>
<ButtonPrimary onClick={WrappedOnDismissed} style={{ margin: '20px 0' }}>
<Text fontWeight={500} fontSize={20}>
Close
</Text>
</ButtonPrimary>
</>
)}
{waitingForConfirmation && <div style={{ height: '138px' }} />}
<Text fontSize={12} color="#565A69">
{waitingForConfirmation
? 'Confirm this transaction in your wallet'
: `Estimated time until confirmation: 3 min`}
</Text>
</AutoColumn>
</Section>
</Wrapper>
)}
</Modal>
)
}

@ -81,7 +81,6 @@ class ContextualInfo extends Component {
if (!this.state.showDetails) {
return null
}
return <Details>{this.props.renderTransactionDetails()}</Details>
}

@ -1,30 +1,23 @@
import React, { useState, useRef, useMemo } from 'react'
import { Link } from 'react-router-dom'
import React, { useState } from 'react'
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'
import Tooltip from '@reach/tooltip'
import '@reach/tooltip/styles.css'
import { isMobile } from 'react-device-detect'
import { BorderlessInput } from '../../theme'
import { useWeb3React, useTokenContract } from '../../hooks'
import { isAddress, calculateGasMargin, formatToUsd, formatTokenBalance, formatEthBalance } from '../../utils'
import { Text } from 'rebass'
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 SearchModal from '../SearchModal'
import { Input as NumericalInput } from '../NumericalInput'
import { RowBetween } from '../Row'
import { useWeb3React } from '../../hooks'
import { useTransactionAdder, usePendingApproval } from '../../contexts/Transactions'
import { useTokenDetails, useAllTokenDetails, INITIAL_TOKENS_CONTEXT } from '../../contexts/Tokens'
import { useToken, useAllTokens } from '../../contexts/Tokens'
import { useTokenContract } from '../../hooks'
import { calculateGasMargin } from '../../utils'
import { useAddressBalance } from '../../contexts/Balances'
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 { useETHPriceInUSD, useAllBalances } from '../../contexts/Balances'
const GAS_MARGIN = ethers.utils.bigNumberify(1000)
@ -51,36 +44,23 @@ const InputRow = styled.div`
padding: 0.25rem 0.85rem 0.75rem;
`
const Input = styled(BorderlessInput)`
font-size: 1.5rem;
color: ${({ error, theme }) => error && theme.salmonRed};
background-color: ${({ theme }) => theme.inputBackground};
-moz-appearance: textfield;
`
const StyledBorderlessInput = styled(BorderlessInput)`
min-height: 2.5rem;
flex-shrink: 0;
text-align: left;
padding-left: 1.6rem;
background-color: ${({ theme }) => theme.concreteGray};
`
const CurrencySelect = styled.button`
align-items: center;
font-size: 1rem;
font-size: 20px;
background-color: ${({ selected, theme }) => (selected ? theme.buttonBackgroundPlain : theme.zumthorBlue)};
color: ${({ selected, theme }) => (selected ? theme.textColor : theme.royalBlue)};
height: 2rem;
border: 1px solid ${({ selected, theme }) => (selected ? theme.mercuryGray : theme.royalBlue)};
border-radius: 2.5rem;
background-color: ${({ selected, theme }) => (selected ? theme.concreteGray : theme.zumthorBlue)};
border: 1px solid
${({ selected, theme, disableTokenSelect }) =>
disableTokenSelect ? theme.buttonBackgroundPlain : selected ? theme.buttonOutlinePlain : theme.royalBlue};
border-radius: 8px;
outline: none;
cursor: pointer;
user-select: none;
:hover {
border: 1px solid
${({ selected, theme }) => (selected ? darken(0.1, theme.mercuryGray) : darken(0.1, theme.royalBlue))};
${({ selected, theme }) => (selected ? darken(0.1, theme.outlineGrey) : darken(0.1, theme.royalBlue))};
}
:focus {
@ -109,7 +89,6 @@ const StyledDropDown = styled(DropDown)`
const InputPanel = styled.div`
${({ theme }) => theme.flexColumnNoWrap}
box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.95, theme.shadowColor)};
position: relative;
border-radius: 1.25rem;
background-color: ${({ theme }) => theme.inputBackground};
@ -122,7 +101,7 @@ const Container = styled.div`
background-color: ${({ theme }) => theme.inputBackground};
:focus-within {
border: 1px solid ${({ theme }) => theme.malibuBlue};
border: 1px solid ${({ error, theme }) => (error ? theme.salmonRed : theme.malibuBlue)};
}
`
@ -139,14 +118,6 @@ const LabelRow = styled.div`
}
`
const LabelContainer = styled.div`
flex: 1 1 auto;
width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
`
const ErrorSpan = styled.span`
color: ${({ error, theme }) => error && theme.salmonRed};
:hover {
@ -155,158 +126,79 @@ const ErrorSpan = styled.span`
}
`
const TokenModal = styled.div`
${({ theme }) => theme.flexColumnNoWrap}
width: 100%;
`
const ModalHeader = styled.div`
position: relative;
display: flex;
flex-direction: row;
align-items: center;
padding: 0px 0px 0px 1rem;
height: 60px;
`
const CloseColor = styled(Close)`
path {
stroke: ${({ theme }) => theme.textColor};
}
`
const CloseIcon = styled.div`
position: absolute;
right: 1rem;
top: 14px;
&:hover {
cursor: pointer;
opacity: 0.6;
}
`
const SearchContainer = styled.div`
${({ theme }) => theme.flexRowNoWrap}
justify-content: flex-start;
padding: 0.5rem 1.5rem;
background-color: ${({ theme }) => theme.concreteGray};
`
const TokenModalInfo = styled.div`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
padding: 1rem 1.5rem;
margin: 0.25rem 0.5rem;
justify-content: center;
user-select: none;
`
const TokenList = styled.div`
flex-grow: 1;
height: 100%;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
`
const TokenModalRow = styled.div`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
justify-content: space-between;
padding: 1rem;
cursor: pointer;
user-select: none;
#symbol {
color: ${({ theme }) => theme.doveGrey};
}
:hover {
background-color: ${({ theme }) => theme.tokenRowHover};
}
${({ theme }) => theme.mediaWidth.upToMedium`
padding: 0.8rem 1rem;
padding-right: 2rem;
`}
`
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 FadedSpan = styled.span`
color: ${({ theme }) => theme.royalBlue};
`
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;
margin: 0 0.25rem 0 0.75rem;
`
const SpinnerWrapper = styled(Spinner)`
margin: 0 0.25rem 0 0.25rem;
color: ${({ theme }) => theme.chaliceGray};
opacity: 0.6;
const ClickableText = styled.div`
:hover {
cursor: pointer;
}
`
const StyledBalanceMax = styled.button`
height: 30px;
background-color: ${({ theme }) => theme.zumthorBlue};
border: 1px solid ${({ theme }) => theme.zumthorBlue};
border-radius: 0.5rem;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
margin-right: 0.5rem;
color: ${({ theme }) => theme.royalBlue};
:hover {
border: 1px solid ${({ theme }) => theme.royalBlue};
}
:focus {
border: 1px solid ${({ theme }) => theme.royalBlue};
outline: none;
}
`
export default function CurrencyInputPanel({
onValueChange = () => {},
allBalances,
renderInput,
onCurrencySelected = () => {},
title,
description,
extraText,
extraTextClickHander = () => {},
errorMessage,
disableUnlock,
disableTokenSelect,
selectedTokenAddress = '',
showUnlock,
value,
urlAddedTokens
field,
onUserInput,
selectedTokenAddress,
onTokenSelection,
title,
onMax,
atMax,
error
// disableUnlock,
// disableTokenSelect,
// urlAddedTokens
}) {
const { account } = useWeb3React()
const { t } = useTranslation()
const disableUnlock = false
const disableTokenSelect = false
const urlAddedTokens = []
const errorMessage = error
const [modalIsOpen, setModalIsOpen] = useState(false)
const tokenContract = useTokenContract(selectedTokenAddress)
const { exchangeAddress: selectedTokenExchangeAddress } = useTokenDetails(selectedTokenAddress)
const { exchangeAddress: selectedTokenExchangeAddress } = useToken(selectedTokenAddress)
const pendingApproval = usePendingApproval(selectedTokenAddress)
const addTransaction = useTransactionAdder()
const allTokens = useAllTokenDetails()
const allTokens = useAllTokens()
const { account } = useWeb3React()
const token = useToken(selectedTokenAddress)
const userTokenBalance = useAddressBalance(account, selectedTokenAddress)
const userTokenBalance = useAddressBalance(account, token)
const [showUnlock] = useState(false)
const [showMax, setShowMax] = useState(false)
function renderUnlockButton() {
if (disableUnlock || !showUnlock || selectedTokenAddress === 'ETH' || !selectedTokenAddress) {
@ -350,31 +242,28 @@ export default function CurrencyInputPanel({
}
}
function _renderInput() {
if (typeof renderInput === 'function') {
return renderInput()
}
return (
<InputPanel>
<Container error={!!errorMessage}>
<LabelRow>
<RowBetween>
<Text>{title}</Text>
<ErrorSpan data-tip={'Enter max'} error={!!errorMessage} onClick={() => {}}></ErrorSpan>
<ClickableText onClick={onMax}>
<Text>Balance: {userTokenBalance?.toFixed(2)}</Text>
</ClickableText>
</RowBetween>
</LabelRow>
<InputRow>
<Input
type="number"
min="0"
step="0.000000000000000001"
error={!!errorMessage}
placeholder="0.0"
onChange={e => onValueChange(e.target.value)}
onKeyPress={e => {
const charCode = e.which ? e.which : e.keyCode
// Prevent 'minus' character
if (charCode === 45) {
e.preventDefault()
e.stopPropagation()
}
}}
<NumericalInput
field={field}
value={value}
onUserInput={onUserInput}
onFocus={() => {
setShowMax(true)
}}
/>
{!!selectedTokenAddress && !atMax && showMax && <StyledBalanceMax onClick={onMax}>MAX</StyledBalanceMax>}
{renderUnlockButton()}
<CurrencySelect
selected={!!selectedTokenAddress}
@ -383,9 +272,10 @@ export default function CurrencyInputPanel({
setModalIsOpen(true)
}
}}
disableTokenSelect={disableTokenSelect}
>
<Aligner>
{selectedTokenAddress ? <TokenLogo address={selectedTokenAddress} /> : null}
{selectedTokenAddress ? <TokenLogo address={selectedTokenAddress} size={'24px'} /> : null}
{
<StyledTokenName>
{(allTokens[selectedTokenAddress] && allTokens[selectedTokenAddress].symbol) || t('selectToken')}
@ -395,290 +285,19 @@ export default function CurrencyInputPanel({
</Aligner>
</CurrencySelect>
</InputRow>
)
}
return (
<InputPanel>
<Container error={!!errorMessage}>
<LabelRow>
<LabelContainer>
<span>{title}</span> <span>{description}</span>
</LabelContainer>
<ErrorSpan
data-tip={'Enter max'}
error={!!errorMessage}
onClick={() => {
extraTextClickHander()
}}
>
<Tooltip
label="Enter Max"
style={{
background: 'hsla(0, 0%, 0%, 0.75)',
color: 'white',
border: 'none',
borderRadius: '24px',
padding: '0.5em 1em',
marginTop: '-64px'
}}
>
<span>{extraText}</span>
</Tooltip>
</ErrorSpan>
</LabelRow>
{_renderInput()}
</Container>
{!disableTokenSelect && (
<CurrencySelectModal
<SearchModal
isOpen={modalIsOpen}
onDismiss={() => {
setModalIsOpen(false)
}}
filterType="tokens"
urlAddedTokens={urlAddedTokens}
onTokenSelect={onCurrencySelected}
allBalances={allBalances}
field={field}
onTokenSelect={onTokenSelection}
/>
)}
</InputPanel>
)
}
function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect, urlAddedTokens }) {
const { t } = useTranslation()
const [searchQuery, setSearchQuery] = useState('')
const { exchangeAddress } = useTokenDetails(searchQuery)
const allTokens = useAllTokenDetails()
const { account, chainId } = useWeb3React()
// BigNumber.js instance
const ethPrice = useETHPriceInUSD()
// all balances for both account and exchanges
let allBalances = useAllBalances()
const _usdAmounts = Object.keys(allTokens).map(k => {
if (ethPrice && allBalances[account] && allBalances[account][k] && allBalances[account][k].value) {
let ethRate = 1 // default for ETH
let exchangeDetails = allBalances[allTokens[k].exchangeAddress]
if (
exchangeDetails &&
exchangeDetails[k] &&
exchangeDetails[k].value &&
exchangeDetails['ETH'] &&
exchangeDetails['ETH'].value
) {
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 {
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) => {
if (allTokens[a].symbol && allTokens[b].symbol) {
const aSymbol = allTokens[a].symbol.toLowerCase()
const bSymbol = allTokens[b].symbol.toLowerCase()
// pin ETH to top
if (aSymbol === 'ETH'.toLowerCase() || bSymbol === 'ETH'.toLowerCase()) {
return aSymbol === bSymbol ? 0 : aSymbol === 'ETH'.toLowerCase() ? -1 : 1
}
// then tokens with balance
if (usdAmounts[a] && !usdAmounts[b]) {
return -1
} else if (usdAmounts[b] && !usdAmounts[a]) {
return 1
}
// sort by balance
if (usdAmounts[a] && usdAmounts[b]) {
const aUSD = usdAmounts[a]
const bUSD = usdAmounts[b]
return aUSD.gt(bUSD) ? -1 : aUSD.lt(bUSD) ? 1 : 0
}
// sort alphabetically
return aSymbol < bSymbol ? -1 : aSymbol > bSymbol ? 1 : 0
} else {
return 0
}
})
.map(k => {
let balance
let usdBalance
// only update if we have data
if (k === 'ETH' && allBalances[account] && allBalances[account][k] && allBalances[account][k].value) {
balance = formatEthBalance(allBalances[account][k].value)
usdBalance = usdAmounts[k]
} else if (allBalances[account] && allBalances[account][k] && allBalances[account][k].value) {
balance = formatTokenBalance(allBalances[account][k].value, allTokens[k].decimals)
usdBalance = usdAmounts[k]
}
return {
name: allTokens[k].name,
symbol: allTokens[k].symbol,
address: k,
balance: balance,
usdBalance: usdBalance
}
})
}, [allBalances, allTokens, usdAmounts, account])
const filteredTokenList = useMemo(() => {
return tokenList.filter(tokenEntry => {
const inputIsAddress = searchQuery.slice(0, 2) === '0x'
// check the regex for each field
const regexMatches = Object.keys(tokenEntry).map(tokenEntryKey => {
// if address field only search if input starts with 0x
if (tokenEntryKey === 'address') {
return (
inputIsAddress &&
typeof tokenEntry[tokenEntryKey] === 'string' &&
!!tokenEntry[tokenEntryKey].match(new RegExp(escapeStringRegex(searchQuery), 'i'))
)
}
return (
typeof tokenEntry[tokenEntryKey] === 'string' &&
!!tokenEntry[tokenEntryKey].match(new RegExp(escapeStringRegex(searchQuery), 'i'))
)
})
return regexMatches.some(m => m)
})
}, [tokenList, searchQuery])
function _onTokenSelect(address) {
setSearchQuery('')
onTokenSelect(address)
onDismiss()
}
function renderTokenList() {
if (isAddress(searchQuery) && exchangeAddress === undefined) {
return <TokenModalInfo>Searching for Exchange...</TokenModalInfo>
}
if (isAddress(searchQuery) && exchangeAddress === ethers.constants.AddressZero) {
return (
<>
<TokenModalInfo>{t('noExchange')}</TokenModalInfo>
<TokenModalInfo>
<Link to={`/create-exchange/${searchQuery}`}>{t('createExchange')}</Link>
</TokenModalInfo>
</>
)
}
if (!filteredTokenList.length) {
return <TokenModalInfo>{t('noExchange')}</TokenModalInfo>
}
return filteredTokenList.map(({ address, symbol, name, balance, usdBalance }) => {
const urlAdded = urlAddedTokens && urlAddedTokens.hasOwnProperty(address)
const customAdded =
address !== 'ETH' &&
INITIAL_TOKENS_CONTEXT[chainId] &&
!INITIAL_TOKENS_CONTEXT[chainId].hasOwnProperty(address) &&
!urlAdded
return (
<TokenModalRow key={address} onClick={() => _onTokenSelect(address)}>
<TokenRowLeft>
<TokenLogo address={address} size={'2rem'} />
<TokenSymbolGroup>
<div>
<span id="symbol">{symbol}</span>
<FadedSpan>
{urlAdded && '(Added by URL)'} {customAdded && '(Added by user)'}
</FadedSpan>
</div>
<TokenFullName> {name}</TokenFullName>
</TokenSymbolGroup>
</TokenRowLeft>
<TokenRowRight>
{balance ? (
<TokenRowBalance>{balance && (balance > 0 || balance === '<0.0001') ? balance : '-'}</TokenRowBalance>
) : account ? (
<SpinnerWrapper src={Circle} alt="loader" />
) : (
'-'
)}
<TokenRowUsd>
{usdBalance && !usdBalance.isNaN()
? usdBalance.isZero()
? ''
: usdBalance.lt(0.01)
? '<$0.01'
: '$' + formatToUsd(usdBalance)
: ''}
</TokenRowUsd>
</TokenRowRight>
</TokenModalRow>
)
})
}
// manage focus on modal show
const inputRef = useRef()
function onInput(event) {
const input = event.target.value
const checksummedInput = isAddress(input)
setSearchQuery(checksummedInput || input)
}
function clearInputAndDismiss() {
setSearchQuery('')
onDismiss()
}
return (
<Modal
isOpen={isOpen}
onDismiss={clearInputAndDismiss}
minHeight={60}
maxHeight={50}
initialFocusRef={isMobile ? undefined : inputRef}
>
<TokenModal>
<ModalHeader>
<p>Select Token</p>
<CloseIcon onClick={clearInputAndDismiss}>
<CloseColor alt={'close icon'} />
</CloseIcon>
</ModalHeader>
<SearchContainer>
<img src={SearchIcon} alt="search" />
<StyledBorderlessInput
ref={inputRef}
type="text"
placeholder={isMobile ? t('searchOrPasteMobile') : t('searchOrPaste')}
onChange={onInput}
/>
</SearchContainer>
<TokenList>{renderTokenList()}</TokenList>
</TokenModal>
</Modal>
)
}

@ -0,0 +1,28 @@
import React from 'react'
import styled from 'styled-components'
import TokenLogo from '../TokenLogo'
export default function DoubleTokenLogo({ a0, a1, size = 16, margin = false }) {
const TokenWrapper = styled.div`
position: relative;
display: flex;
flex-direction: row;
margin-right: ${({ sizeraw, margin }) => margin && (sizeraw / 2 + 10).toString() + 'px'};
`
const HigherLogo = styled(TokenLogo)`
z-index: 2;
`
const CoveredLogo = styled(TokenLogo)`
position: absolute;
left: ${({ sizeraw }) => (sizeraw / 2).toString() + 'px'};
`
return (
<TokenWrapper sizeraw={size} margin={margin}>
<HigherLogo address={a0} size={size.toString() + 'px'} />
<CoveredLogo address={a1} size={size.toString() + 'px'} sizeraw={size} />
</TokenWrapper>
)
}

@ -4,14 +4,13 @@ import { createBrowserHistory } from 'history'
import { ethers } from 'ethers'
import styled from 'styled-components'
import { useTranslation } from 'react-i18next'
import { BigNumber } from '@uniswap/sdk'
import { useWeb3React } from '../../hooks'
import { brokenTokens } from '../../constants'
import { amountFormatter, calculateGasMargin, isAddress } from '../../utils'
import { useExchangeContract } from '../../hooks'
import { useTokenDetails, INITIAL_TOKENS_CONTEXT } from '../../contexts/Tokens'
import { useToken, INITIAL_TOKENS_CONTEXT } from '../../contexts/Tokens'
import { useTransactionAdder } from '../../contexts/Transactions'
import { useAddressBalance, useExchangeReserves } from '../../contexts/Balances'
import { useAddressAllowance } from '../../contexts/Allowances'
@ -205,7 +204,6 @@ function getExchangeRate(inputValue, inputDecimals, outputValue, outputDecimals,
(outputDecimals || outputDecimals === 0)
) {
const factor = ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18))
if (invert) {
return inputValue
.mul(factor)
@ -334,10 +332,10 @@ export default function ExchangePage({ initialCurrency, sending = false, params
const swapType = getSwapType(inputCurrency, outputCurrency)
// get decimals and exchange address for each of the currency types
const { symbol: inputSymbol, decimals: inputDecimals, exchangeAddress: inputExchangeAddress } = useTokenDetails(
const { symbol: inputSymbol, decimals: inputDecimals, exchangeAddress: inputExchangeAddress } = useToken(
inputCurrency
)
const { symbol: outputSymbol, decimals: outputDecimals, exchangeAddress: outputExchangeAddress } = useTokenDetails(
const { symbol: outputSymbol, decimals: outputDecimals, exchangeAddress: outputExchangeAddress } = useToken(
outputCurrency
)
@ -353,8 +351,8 @@ export default function ExchangePage({ initialCurrency, sending = false, params
const { reserveETH: outputReserveETH, reserveToken: outputReserveToken } = useExchangeReserves(outputCurrency)
// get balances for each of the currency types
const inputBalance = useAddressBalance(account, inputCurrency)
const outputBalance = useAddressBalance(account, outputCurrency)
const inputBalance = 0
const outputBalance = 0
const inputBalanceFormatted = !!(inputBalance && Number.isInteger(inputDecimals))
? amountFormatter(inputBalance, inputDecimals, Math.min(4, inputDecimals))
: ''
@ -631,8 +629,8 @@ export default function ExchangePage({ initialCurrency, sending = false, params
} else if (outputCurrency === 'ETH') {
ethTransactionSize = inputValueFormatted * amountFormatter(exchangeRate, 18, 6, false)
} else {
const tokenBalance = inputReserveToken && new BigNumber(inputReserveToken.toString())
const ethBalance = inputReserveETH && new BigNumber(inputReserveETH.toString())
const tokenBalance = 1
const ethBalance = 1
let ethRate = ethBalance && tokenBalance && ethBalance.div(tokenBalance)
ethTransactionSize = inputValueFormatted * ethRate
}

@ -0,0 +1,15 @@
import React from 'react'
import styled from 'styled-components'
import { Spinner } from '../../theme'
import Circle from '../../assets/images/blue-loader.svg'
const SpinnerWrapper = styled(Spinner)`
height: ${({ size }) => size};
width: ${({ size }) => size};
`
export default function Loader({ size }) {
return <SpinnerWrapper src={Circle} alt="loader" size={size} />
}

@ -54,7 +54,7 @@ const StyledDialogContent = styled(FilteredDialogContent)`
padding: 0px;
width: 50vw;
max-width: 650px;
max-width: 500px;
${({ maxHeight }) =>
maxHeight &&
css`

@ -1,18 +1,16 @@
import React, { useCallback } from 'react'
import { withRouter, NavLink } from 'react-router-dom'
import { withRouter, NavLink, Link as HistoryLink } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import { transparentize, darken } from 'polished'
import { useWeb3React, useBodyKeyDown } from '../../hooks'
import { useAddressBalance } from '../../contexts/Balances'
import { isAddress } from '../../utils'
import {
useBetaMessageManager,
useSaiHolderMessageManager,
useGeneralDaiMessageManager
} from '../../contexts/LocalStorage'
import { Link } from '../../theme/components'
import { RowBetween } from '../Row'
import QuestionHelper from '../Question'
import { ArrowLeft } from 'react-feather'
import { useBodyKeyDown } from '../../hooks'
import { useBetaMessageManager } from '../../contexts/LocalStorage'
const tabOrder = [
{
@ -26,9 +24,9 @@ const tabOrder = [
regex: /\/send/
},
{
path: '/add-liquidity',
path: '/supply',
textKey: 'pool',
regex: /\/add-liquidity|\/remove-liquidity|\/create-exchange.*/
regex: /\/supply/
}
]
@ -61,57 +59,12 @@ const BetaMessage = styled.div`
}
`
const DaiMessage = styled(BetaMessage)`
${({ theme }) => theme.flexColumnNoWrap}
position: relative;
word-wrap: wrap;
overflow: visible;
white-space: normal;
padding: 1rem 1rem;
padding-right: 2rem;
line-height: 1.2rem;
cursor: default;
color: ${({ theme }) => theme.textColor};
div {
width: 100%;
}
&:after {
content: '';
}
`
const CloseIcon = styled.div`
width: 10px !important;
top: 0.5rem;
right: 1rem;
position: absolute;
color: ${({ theme }) => theme.wisteriaPurple};
:hover {
cursor: pointer;
}
`
const WarningHeader = styled.div`
margin-bottom: 10px;
font-weight: 500;
color: ${({ theme }) => theme.uniswapPink};
`
const WarningFooter = styled.div`
margin-top: 10px;
font-size: 10px;
text-decoration: italic;
color: ${({ theme }) => theme.greyText};
`
const Tabs = styled.div`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
height: 2.5rem;
background-color: ${({ theme }) => theme.concreteGray};
height: 3rem;
border-radius: 3rem;
/* border: 1px solid ${({ theme }) => theme.mercuryGray}; */
margin-bottom: 1rem;
margin-bottom: 20px;
`
const activeClassName = 'ACTIVE'
@ -123,53 +76,42 @@ const StyledNavLink = styled(NavLink).attrs({
align-items: center;
justify-content: center;
height: 2.5rem;
border: 1px solid ${({ theme }) => transparentize(1, theme.mercuryGray)};
flex: 1 0 auto;
border-radius: 3rem;
outline: none;
cursor: pointer;
text-decoration: none;
color: ${({ theme }) => theme.doveGray};
font-size: 1rem;
font-size: 20px;
box-sizing: border-box;
&.${activeClassName} {
background-color: ${({ theme }) => theme.inputBackground};
border-radius: 3rem;
border: 1px solid ${({ theme }) => theme.mercuryGray};
box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.95, theme.shadowColor)};
box-sizing: border-box;
font-weight: 500;
color: ${({ theme }) => theme.royalBlue};
:hover {
/* border: 1px solid ${({ theme }) => darken(0.1, theme.mercuryGray)}; */
background-color: ${({ theme }) => darken(0.01, theme.inputBackground)};
}
color: ${({ theme }) => theme.black};
}
:hover,
:focus {
color: ${({ theme }) => darken(0.1, theme.royalBlue)};
color: ${({ theme }) => darken(0.1, theme.black)};
}
`
const ActiveText = styled.div`
font-weight: 500;
font-size: 20px;
`
const ArrowLink = styled(ArrowLeft)`
color: ${({ theme }) => theme.black};
`
function NavigationTabs({ location: { pathname }, history }) {
const { t } = useTranslation()
const [showBetaMessage, dismissBetaMessage] = useBetaMessageManager()
const [showGeneralDaiMessage, dismissGeneralDaiMessage] = useGeneralDaiMessageManager()
const [showSaiHolderMessage, dismissSaiHolderMessage] = useSaiHolderMessageManager()
const { account } = useWeb3React()
const daiBalance = useAddressBalance(account, isAddress('0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359'))
const daiPoolTokenBalance = useAddressBalance(account, isAddress('0x09cabEC1eAd1c0Ba254B09efb3EE13841712bE14'))
const onLiquidityPage = pathname === '/pool' || pathname === '/add-liquidity' || pathname === '/remove-liquidity'
const navigate = useCallback(
direction => {
const tabIndex = tabOrder.findIndex(({ regex }) => pathname.match(regex))
@ -187,12 +129,21 @@ function NavigationTabs({ location: { pathname }, history }) {
useBodyKeyDown('ArrowRight', navigateRight)
useBodyKeyDown('ArrowLeft', navigateLeft)
const providerMessage =
showSaiHolderMessage && daiPoolTokenBalance && !daiPoolTokenBalance.isZero() && onLiquidityPage
const generalMessage = showGeneralDaiMessage && daiBalance && !daiBalance.isZero()
const adding = pathname.match('/add')
return (
<>
{adding ? (
<Tabs>
<RowBetween style={{ padding: '1rem' }}>
<HistoryLink to="/supply">
<ArrowLink />
</HistoryLink>
<ActiveText>Add Liquidity</ActiveText>
<QuestionHelper text={'helper text'} />
</RowBetween>
</Tabs>
) : (
<Tabs>
{tabOrder.map(({ path, textKey, regex }) => (
<StyledNavLink key={path} to={path} isActive={(_, { pathname }) => pathname.match(regex)}>
@ -200,41 +151,8 @@ function NavigationTabs({ location: { pathname }, history }) {
</StyledNavLink>
))}
</Tabs>
{providerMessage && (
<DaiMessage>
<CloseIcon onClick={dismissSaiHolderMessage}></CloseIcon>
<WarningHeader>Missing your DAI?</WarningHeader>
<div>
Dont worry, check the{' '}
<Link href={'/remove-liquidity?poolTokenAddress=0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359'}>
SAI liquidity pool.
</Link>{' '}
Your old DAI is now SAI. If you want to migrate,{' '}
<Link href="/remove-liquidity?poolTokenAddress=0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359">
remove your SAI liquidity,
</Link>{' '}
migrate using the <Link href="https://migrate.makerdao.com/">migration tool</Link> then add your migrated
DAI to the{' '}
<Link href="add-liquidity?token=0x6B175474E89094C44Da98b954EedeAC495271d0F">new DAI liquidity pool.</Link>
</div>
<WarningFooter>
<Link href="https://blog.makerdao.com/looking-ahead-how-to-upgrade-to-multi-collateral-dai/">
Read more
</Link>{' '}
about this change on the official Maker blog.
</WarningFooter>
</DaiMessage>
)}
{generalMessage && !providerMessage && (
<DaiMessage>
<CloseIcon onClick={dismissGeneralDaiMessage}></CloseIcon>
<WarningHeader>DAI has upgraded!</WarningHeader>
<div>
Your old DAI is now SAI. To upgrade use the{' '}
<Link href="https://migrate.makerdao.com/">migration tool.</Link>
</div>
</DaiMessage>
)}
{showBetaMessage && (
<BetaMessage onClick={dismissBetaMessage}>
<span role="img" aria-label="warning">

@ -0,0 +1,64 @@
import React from 'react'
import styled from 'styled-components'
const StyledInput = styled.input`
color: ${({ error, theme }) => error && theme.salmonRed};
background-color: ${({ theme }) => theme.inputBackground};
color: ${({ theme }) => theme.textColor};
font-size: 20px;
outline: none;
border: none;
flex: 1 1 auto;
width: 0;
background-color: ${({ theme }) => theme.inputBackground};
[type='number'] {
-moz-appearance: textfield;
}
::-webkit-outer-spin-button,
::-webkit-inner-spin-button {
-webkit-appearance: none;
}
::placeholder {
color: ${({ theme }) => theme.chaliceGray};
}
`
const inputRegex = RegExp(`^\\d*(?:\\\\.)?\\d*$`) // match escaped "." characters via in a non-capturing group
function escapeRegExp(string: string): string {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
}
export const Input = React.memo(({ field, value, onUserInput, ...rest }: any) => {
function enforcer(nextUserInput: string) {
if (nextUserInput === '' || inputRegex.test(escapeRegExp(nextUserInput))) {
onUserInput(field, nextUserInput)
}
}
return (
<StyledInput
{...rest}
value={value}
onChange={event => {
enforcer(event.target.value)
}}
// universal input options
inputMode="decimal"
title="Token Amount"
autoComplete="off"
autoCorrect="off"
// text-specific options
type="text"
placeholder="0.0"
minLength={1}
maxLength={79}
spellCheck="false"
/>
)
})
export default Input

@ -0,0 +1,90 @@
import React, { useState } from 'react'
import styled, { keyframes } from 'styled-components'
import question from '../../assets/images/question.svg'
const Wrapper = styled.div`
position: relative;
`
const QuestionWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
margin-left: 0.4rem;
padding: 0.2rem;
border: none;
background: none;
outline: none;
cursor: default;
border-radius: 36px;
:hover,
:focus {
opacity: 0.7;
}
`
const HelpCircleStyled = styled.img`
height: 24px;
width: 23px;
`
const fadeIn = keyframes`
from {
opacity : 0;
}
to {
opacity : 1;
}
`
const Popup = styled.div`
position: absolute;
width: 228px;
z-index: 9999;
left: 40px;
top: -10px;
display: flex;
flex-direction: column;
align-items: center;
padding: 0.6rem 1rem;
line-height: 150%;
background: ${({ theme }) => theme.inputBackground};
border: 1px solid ${({ theme }) => theme.mercuryGray};
border-radius: 8px;
animation: ${fadeIn} 0.15s linear;
color: ${({ theme }) => theme.textColor};
font-style: italic;
${({ theme }) => theme.mediaWidth.upToSmall`
left: -20px;
`}
`
export default function QuestionHelper({ text }) {
const [showPopup, setPopup] = useState(false)
return (
<Wrapper>
<QuestionWrapper
onClick={() => {
setPopup(!showPopup)
}}
onMouseEnter={() => {
setPopup(true)
}}
onMouseLeave={() => {
setPopup(false)
}}
>
<HelpCircleStyled src={question} alt="popup" />
</QuestionWrapper>
{showPopup && <Popup>{text}</Popup>}
</Wrapper>
)
}

@ -0,0 +1,31 @@
import styled from 'styled-components'
const Row = styled.div`
width: 100%;
display: flex;
align-items: center;
`
export const RowBetween = styled(Row)`
justify-content: space-between;
`
export const RowFlat = styled.div`
display: flex;
align-items: flex-end;
`
export const AutoRow = styled(Row)`
flex-wrap: wrap;
margin: -${({ gap }) => gap};
& > * {
margin: ${({ gap }) => gap} !important;
}
`
export const RowFixed = styled(Row)`
width: fit-content;
`
export default Row

@ -0,0 +1,398 @@
import React, { useState, useRef, useMemo, useEffect } from 'react'
import { withRouter } from 'react-router-dom'
import { Link } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { ethers } from 'ethers'
import styled from 'styled-components'
import escapeStringRegex from 'escape-string-regexp'
import '@reach/tooltip/styles.css'
import { isMobile } from 'react-device-detect'
import { Text } from 'rebass'
import Column, { AutoColumn } from '../Column'
import { RowBetween, RowFixed } from '../Row'
import TokenLogo from '../TokenLogo'
import { CloseIcon } from '../../theme/components'
import DoubleTokenLogo from '../DoubleLogo'
import { useWeb3React } from '../../hooks'
import { isAddress } from '../../utils'
import Modal from '../Modal'
import { useToken, useAllTokens, INITIAL_TOKENS_CONTEXT } from '../../contexts/Tokens'
import { Spinner } from '../../theme'
import Circle from '../../assets/images/circle.svg'
import { useAllBalances } from '../../contexts/Balances'
import { useAllExchanges } from '../../contexts/Exchanges'
const TokenModalInfo = styled.div`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
padding: 1rem 1.5rem;
margin: 0.25rem 0.5rem;
justify-content: center;
user-select: none;
`
const TokenList = styled.div`
flex-grow: 1;
height: 100%;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
`
const FadedSpan = styled.span`
color: ${({ theme }) => theme.royalBlue};
`
const SpinnerWrapper = styled(Spinner)`
margin: 0 0.25rem 0 0.25rem;
color: ${({ theme }) => theme.chaliceGray};
opacity: 0.6;
`
const Input = styled.input`
position: relative;
display: flex;
padding: 16px;
align-items: center;
width: 100%;
white-space: nowrap;
background: none;
border: none;
outline: none;
border: 1px solid #edeef2;
box-sizing: border-box;
border-radius: 20px;
color: ${({ theme }) => theme.textColor};
font-size: 18px;
::placeholder {
color: ${({ theme }) => theme.fadedText};
}
`
const TokenModal = styled.div`
${({ theme }) => theme.flexColumnNoWrap}
width: 100%;
`
const FilterWrapper = styled(RowFixed)`
padding: 8px;
background-color: ${({ selected, theme }) => selected && theme.backgroundColor};
color: ${({ selected, theme }) => (selected ? theme.black : '#888D9B')};
border-radius: 8px;
user-select: none;
& > * {
user-select: none;
}
:hover {
cursor: pointer;
}
`
const PaddedColumn = styled(AutoColumn)`
padding: 24px;
padding-bottom: 12px;
`
const MenuItem = styled(RowBetween)`
padding: 4px 24px;
width: calc(100% - 48px);
height: 56px;
cursor: pointer;
:hover {
background-color: ${({ theme }) => theme.tokenRowHover};
}
`
function SearchModal({ history, isOpen, onDismiss, onTokenSelect, field, urlAddedTokens, filterType }) {
const { t } = useTranslation()
const [searchQuery, setSearchQuery] = useState('')
const { exchangeAddress } = useToken(searchQuery)
const allTokens = useAllTokens()
const { account, chainId } = useWeb3React()
// all balances for both account and exchanges
let allBalances = useAllBalances()
const tokenList = useMemo(() => {
return Object.keys(allTokens)
.sort((a, b) => {
if (allTokens[a].symbol && allTokens[b].symbol) {
const aSymbol = allTokens[a].symbol.toLowerCase()
const bSymbol = allTokens[b].symbol.toLowerCase()
// pin ETH to top
if (aSymbol === 'ETH'.toLowerCase() || bSymbol === 'ETH'.toLowerCase()) {
return aSymbol === bSymbol ? 0 : aSymbol === 'ETH'.toLowerCase() ? -1 : 1
}
// sort alphabetically
return aSymbol < bSymbol ? -1 : aSymbol > bSymbol ? 1 : 0
} else {
return 0
}
})
.map(k => {
let balance
// only update if we have data
if (k === 'ETH' && allBalances[account] && allBalances[account][k] && allBalances[account][k].value) {
balance = allBalances[account][k].value
} else if (allBalances[account] && allBalances[account][k] && allBalances[account][k].value) {
balance = (allBalances[account][k].value, allTokens[k].decimals)
}
return {
name: allTokens[k].name,
symbol: allTokens[k].symbol,
address: k,
balance: balance
}
})
}, [allBalances, allTokens, account])
const filteredTokenList = useMemo(() => {
return tokenList.filter(tokenEntry => {
const inputIsAddress = searchQuery.slice(0, 2) === '0x'
// check the regex for each field
const regexMatches = Object.keys(tokenEntry).map(tokenEntryKey => {
// if address field only search if input starts with 0x
if (tokenEntryKey === 'address') {
return (
inputIsAddress &&
typeof tokenEntry[tokenEntryKey] === 'string' &&
!!tokenEntry[tokenEntryKey].match(new RegExp(escapeStringRegex(searchQuery), 'i'))
)
}
return (
typeof tokenEntry[tokenEntryKey] === 'string' &&
!!tokenEntry[tokenEntryKey].match(new RegExp(escapeStringRegex(searchQuery), 'i'))
)
})
return regexMatches.some(m => m)
})
}, [tokenList, searchQuery])
function _onTokenSelect(address) {
setSearchQuery('')
onTokenSelect(field, address)
onDismiss()
}
// manage focus on modal show
const inputRef = useRef()
function onInput(event) {
const input = event.target.value
const checksummedInput = isAddress(input)
setSearchQuery(checksummedInput || input)
}
function clearInputAndDismiss() {
setSearchQuery('')
onDismiss()
}
// get all exchanges
const allExchanges = useAllExchanges()
// amount of tokens to display at once
const [tokensShown, setTokensShown] = useState(0)
const [pairsShown, setPairsShown] = useState(0)
// filters on results
const FILTERS = {
VOLUME: 'VOLUME',
LIQUIDITY: 'LIQUIDITY',
BALANCES: 'BALANCES'
}
const [activeFilter, setActiveFilter] = useState(FILTERS.BALANCES)
const [sortDirection, setSortDirection] = useState(true)
// sort tokens
const escapeStringRegexp = string => string
// sort pairs
const filteredPairList = useMemo(() => {
// check if the search is an address
const isAddress = searchQuery.slice(0, 2) === '0x'
return Object.keys(allExchanges).filter(token0Address => {
return Object.keys(allExchanges[token0Address]).map(token1Address => {
if (searchQuery === '') {
return true
}
const token0 = allTokens[token0Address]
const token1 = allTokens[token1Address]
const regexMatches = Object.keys(token0).map(field => {
if (
(field === 'address' && isAddress) ||
(field === 'name' && !isAddress) ||
(field === 'symbol' && !isAddress)
) {
return (
token0[field].match(new RegExp(escapeStringRegexp(searchQuery), 'i')) ||
token1[field].match(new RegExp(escapeStringRegexp(searchQuery), 'i'))
)
}
return false
})
return regexMatches.some(m => m)
})
})
}, [allExchanges, allTokens, searchQuery])
// update the amount shown as filtered list changes
useEffect(() => {
setTokensShown(Math.min(Object.keys(filteredTokenList).length, 3))
}, [filteredTokenList])
useEffect(() => {
setPairsShown(Math.min(Object.keys(filteredPairList).length, 3))
}, [filteredPairList])
function renderPairsList() {
return (
filteredPairList &&
filteredPairList.map((token0Address, i) => {
return Object.keys(allExchanges[token0Address]).map(token1Address => {
const token0 = allTokens[token0Address]
const token1 = allTokens[token1Address]
const exchangeAddress = allExchanges[token0Address][token1Address]
const balance = allBalances?.[account]?.[exchangeAddress]?.toSignificant(6)
return (
<MenuItem
key={i}
onClick={() => {
history.push('/add/' + token0.address + '-' + token1.address)
onDismiss()
}}
>
<RowFixed>
<DoubleTokenLogo a0={token0?.address || ''} a1={token1?.address || ''} size={24} margin={true} />
<Text fontWeight={500} fontSize={16}>{`${token0?.symbol}/${token1?.symbol}`}</Text>
</RowFixed>
<Text fontWeight={500} fontSize={16}>
{balance ? balance.toString() : '-'}
</Text>
</MenuItem>
)
})
})
)
}
function renderTokenList() {
if (isAddress(searchQuery) && exchangeAddress === undefined) {
return <Text>Searching for Exchange...</Text>
}
if (isAddress(searchQuery) && exchangeAddress === ethers.constants.AddressZero) {
return (
<>
<TokenModalInfo>{t('noExchange')}</TokenModalInfo>
<TokenModalInfo>
<Link to={`/create-exchange/${searchQuery}`}>{t('createExchange')}</Link>
</TokenModalInfo>
</>
)
}
if (!filteredTokenList.length) {
return <TokenModalInfo>{t('noExchange')}</TokenModalInfo>
}
return filteredTokenList.map(({ address, symbol, balance }) => {
const urlAdded = urlAddedTokens && urlAddedTokens.hasOwnProperty(address)
const customAdded =
address !== 'ETH' &&
INITIAL_TOKENS_CONTEXT[chainId] &&
!INITIAL_TOKENS_CONTEXT[chainId].hasOwnProperty(address) &&
!urlAdded
return (
<MenuItem key={address} onClick={() => _onTokenSelect(address)}>
<RowFixed>
<TokenLogo address={address} size={'24px'} style={{ marginRight: '14px' }} />
<Column>
<Text fontWeight={500}>{symbol}</Text>
<FadedSpan>
{urlAdded && '(Added by URL)'} {customAdded && '(Added by user)'}
</FadedSpan>
</Column>
</RowFixed>
<AutoColumn gap="4px" justify="end">
{balance ? (
<Text>{balance && (balance > 0 || balance === '<0.0001') ? balance : '-'}</Text>
) : account ? (
<SpinnerWrapper src={Circle} alt="loader" />
) : (
'-'
)}
</AutoColumn>
</MenuItem>
)
})
}
const Filter = ({ title, filter }) => {
return (
<FilterWrapper
onClick={() => {
setActiveFilter(filter)
setSortDirection(!sortDirection)
}}
selected={filter === activeFilter}
>
<Text fontSize={14} fontWeight={500}>
{title}
</Text>
{filter === activeFilter && (
<Text fontSize={14} fontWeight={500}>
{sortDirection ? '↓' : '↑'}
</Text>
)}
</FilterWrapper>
)
}
return (
<Modal
isOpen={isOpen}
onDismiss={clearInputAndDismiss}
minHeight={60}
maxHeight={50}
initialFocusRef={isMobile ? undefined : inputRef}
>
<TokenModal>
<PaddedColumn gap="20px">
<RowBetween>
<Text fontWeight={500} fontSize={16}>
{filterType === 'tokens' ? 'Find A Token' : 'Find A Pool'}
</Text>
<CloseIcon onClick={onDismiss} />
</RowBetween>
<Input
type={'text'}
placeholder={'Search name or address'}
value={searchQuery}
ref={inputRef}
onChange={onInput}
/>
<RowBetween>
<div />
<div />
<Filter title="Your Balances" filter={FILTERS.BALANCES} />
</RowBetween>
</PaddedColumn>
<div style={{ width: '100%', height: '1px', backgroundColor: '#E1E1E1' }} />
<TokenList>{filterType === 'tokens' ? renderTokenList() : renderPairsList()}</TokenList>
</TokenModal>
</Modal>
)
}
export default withRouter(SearchModal)

@ -35,7 +35,7 @@ export default function TokenLogo({ address, size = '1rem', ...rest }) {
let path = ''
if (address === 'ETH') {
return <StyledEthereumLogo size={size} />
return <StyledEthereumLogo size={size} {...rest} />
} else if (!error && !BAD_IMAGES[address]) {
path = TOKEN_ICON_API(address.toLowerCase())
} else {
@ -51,7 +51,7 @@ export default function TokenLogo({ address, size = '1rem', ...rest }) {
return (
<Image
{...rest}
alt={address}
// alt={address}
src={path}
size={size}
onError={() => {

@ -2,7 +2,7 @@ import React, { useState } from 'react'
import styled, { keyframes } from 'styled-components'
import { useWeb3React } from '../../hooks'
import { useTokenDetails } from '../../contexts/Tokens'
import { useToken } from '../../contexts/Tokens'
import { getEtherscanLink } from '../../utils'
import { Link } from '../../theme'
@ -128,7 +128,7 @@ const Text = styled.div`
function WarningCard({ onDismiss, urlAddedTokens, currency }) {
const [showPopup, setPopup] = useState()
const { chainId } = useWeb3React()
const { symbol: inputSymbol, name: inputName } = useTokenDetails(currency)
const { symbol: inputSymbol, name: inputName } = useToken(currency)
const fromURL = urlAddedTokens.hasOwnProperty(currency)
return (

@ -18,7 +18,7 @@ export const network = new NetworkConnector({
})
export const injected = new InjectedConnector({
supportedChainIds: [Number(process.env.REACT_APP_CHAIN_ID)]
supportedChainIds: [Number(process.env.REACT_APP_CHAIN_ID), 4]
})
// mainnet only

@ -1,5 +1,5 @@
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
import {} from '@web3-react/core'
import { Token, TokenAmount } from '@uniswap/sdk'
import { useWeb3React } from '../hooks'
import { safeAccess, isAddress, getTokenAllowance } from '../utils'
@ -7,7 +7,7 @@ import { useBlockNumber } from './Application'
const UPDATE = 'UPDATE'
const AllowancesContext = createContext()
const AllowancesContext = createContext([])
function useAllowancesContext() {
return useContext(AllowancesContext)
@ -54,18 +54,18 @@ export default function Provider({ children }) {
)
}
export function useAddressAllowance(address, tokenAddress, spenderAddress) {
export function useAddressAllowance(address: string, token: Token, spenderAddress: string): TokenAmount {
const { library, chainId } = useWeb3React()
const globalBlockNumber = useBlockNumber()
const [state, { update }] = useAllowancesContext()
const { value, blockNumber } = safeAccess(state, [chainId, address, tokenAddress, spenderAddress]) || {}
const { value, blockNumber } = safeAccess(state, [chainId, address, token.address, spenderAddress]) || {}
useEffect(() => {
if (
isAddress(address) &&
isAddress(tokenAddress) &&
isAddress(token.address) &&
isAddress(spenderAddress) &&
(value === undefined || blockNumber !== globalBlockNumber) &&
(chainId || chainId === 0) &&
@ -73,15 +73,15 @@ export function useAddressAllowance(address, tokenAddress, spenderAddress) {
) {
let stale = false
getTokenAllowance(address, tokenAddress, spenderAddress, library)
getTokenAllowance(address, token.address, spenderAddress, library)
.then(value => {
if (!stale) {
update(chainId, address, tokenAddress, spenderAddress, value, globalBlockNumber)
update(chainId, address, token.address, spenderAddress, value, globalBlockNumber)
}
})
.catch(() => {
if (!stale) {
update(chainId, address, tokenAddress, spenderAddress, null, globalBlockNumber)
update(chainId, address, token.address, spenderAddress, null, globalBlockNumber)
}
})
@ -89,7 +89,8 @@ export function useAddressAllowance(address, tokenAddress, spenderAddress) {
stale = true
}
}
}, [address, tokenAddress, spenderAddress, value, blockNumber, globalBlockNumber, chainId, library, update])
}, [address, token.address, spenderAddress, value, blockNumber, globalBlockNumber, chainId, library, update])
return value
const newTokenAmount: TokenAmount = value ? new TokenAmount(token, value) : null
return newTokenAmount
}

@ -1,310 +0,0 @@
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, 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()
function useBalancesContext() {
return useContext(BalancesContext)
}
function reducer(state, { type, payload }) {
switch (type) {
case UPDATE: {
const { networkId, address, tokenAddress, value, blockNumber } = payload
return {
...state,
[networkId]: {
...(safeAccess(state, [networkId]) || {}),
[address]: {
...(safeAccess(state, [networkId, address]) || {}),
[tokenAddress]: {
value,
blockNumber
}
}
}
}
}
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}'.`)
}
}
}
export default function Provider({ children }) {
const [state, dispatch] = useReducer(reducer, {})
const update = useCallback((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 (
<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()
const globalBlockNumber = useBlockNumber()
const [state, { update }] = useBalancesContext()
const { value, blockNumber } = safeAccess(state, [chainId, address, tokenAddress]) || {}
useEffect(() => {
if (
isAddress(address) &&
(tokenAddress === 'ETH' || isAddress(tokenAddress)) &&
(value === undefined || blockNumber !== globalBlockNumber) &&
(chainId || chainId === 0) &&
library
) {
let stale = false
;(tokenAddress === 'ETH' ? getEtherBalance(address, library) : getTokenBalance(tokenAddress, address, library))
.then(value => {
if (!stale) {
update(chainId, address, tokenAddress, value, globalBlockNumber)
}
})
.catch(() => {
if (!stale) {
update(chainId, address, tokenAddress, null, globalBlockNumber)
}
})
return () => {
stale = true
}
}
}, [address, tokenAddress, value, blockNumber, globalBlockNumber, chainId, library, update])
return value
}
export function useExchangeReserves(tokenAddress) {
const { exchangeAddress } = useTokenDetails(tokenAddress)
const reserveETH = useAddressBalance(exchangeAddress, 'ETH')
const reserveToken = useAddressBalance(exchangeAddress, 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
}

499
src/contexts/Balances.tsx Normal file

@ -0,0 +1,499 @@
import React, { createContext, useContext, useReducer, useRef, useMemo, useCallback, useEffect, ReactNode } from 'react'
import { TokenAmount, Token, JSBI } from '@uniswap/sdk'
import { useWeb3React, useDebounce } from '../hooks'
import { getEtherBalance, getTokenBalance, isAddress } from '../utils'
import { useBlockNumber } from './Application'
import { useAllTokens } from './Tokens'
import { useAllExchanges } from './Exchanges'
const LOCAL_STORAGE_KEY = 'BALANCES'
const SHORT_BLOCK_TIMEOUT = (60 * 2) / 15 // in seconds, represented as a block number delta
const LONG_BLOCK_TIMEOUT = (60 * 15) / 15 // in seconds, represented as a block number delta
const EXCHANGES_BLOCK_TIMEOUT = (60 * 5) / 15 // in seconds, represented as a block number delta
const TRACK_LP_BLANCES = 'TRACK_LP_BLANCES'
const UPDATABLE_KEYS = [TRACK_LP_BLANCES]
interface BalancesState {
[chainId: number]: {
[address: string]: {
[tokenAddress: string]: {
value?: string | null
blockNumber?: number
listenerCount: number
}
}
}
}
function initialize(): BalancesState {
try {
return JSON.parse(window.localStorage.getItem(LOCAL_STORAGE_KEY) as string)
} catch {
return {}
}
}
enum Action {
START_LISTENING,
STOP_LISTENING,
UPDATE,
BATCH_UPDATE_ACCOUNT,
BATCH_UPDATE_EXCHANGES,
UPDATE_KEY
}
function reducer(state: BalancesState, { type, payload }: { type: Action; payload: any }) {
switch (type) {
case Action.START_LISTENING: {
const { chainId, address, tokenAddress } = payload
const uninitialized = !!!state?.[chainId]?.[address]?.[tokenAddress]
return {
...state,
[chainId]: {
...state?.[chainId],
[address]: {
...state?.[chainId]?.[address],
[tokenAddress]: uninitialized
? {
listenerCount: 1
}
: {
...state[chainId][address][tokenAddress],
listenerCount: state[chainId][address][tokenAddress].listenerCount + 1
}
}
}
}
}
case Action.STOP_LISTENING: {
const { chainId, address, tokenAddress } = payload
return {
...state,
[chainId]: {
...state?.[chainId],
[address]: {
...state?.[chainId]?.[address],
[tokenAddress]: {
...state?.[chainId]?.[address]?.[tokenAddress],
listenerCount: state[chainId][address][tokenAddress].listenerCount - 1
}
}
}
}
}
case Action.UPDATE: {
const { chainId, address, tokenAddress, value, blockNumber } = payload
return {
...state,
[chainId]: {
...state?.[chainId],
[address]: {
...state?.[chainId]?.[address],
[tokenAddress]: {
...state?.[chainId]?.[address]?.[tokenAddress],
value,
blockNumber
}
}
}
}
}
case Action.BATCH_UPDATE_ACCOUNT: {
const { chainId, address, tokenAddresses, values, blockNumber } = payload
return {
...state,
[chainId]: {
...state?.[chainId],
[address]: {
...state?.[chainId]?.[address],
...tokenAddresses.reduce((accumulator: any, tokenAddress: string, i: number) => {
const value = values[i]
accumulator[tokenAddress] = {
...state?.[chainId]?.[address]?.[tokenAddress],
value,
blockNumber
}
return accumulator
}, {})
}
}
}
}
case Action.BATCH_UPDATE_EXCHANGES: {
const { chainId, exchangeAddresses, tokenAddresses, values, blockNumber } = payload
return {
...state,
[chainId]: {
...state?.[chainId],
...exchangeAddresses.reduce((accumulator: any, exchangeAddress: string, i: number) => {
const tokenAddress = tokenAddresses[i]
const value = values[i]
accumulator[exchangeAddress] = {
...state?.[chainId]?.[exchangeAddress],
...accumulator?.[exchangeAddress],
[tokenAddress]: {
...state?.[chainId]?.[exchangeAddress]?.[tokenAddress],
value,
blockNumber
}
}
return accumulator
}, {})
}
}
}
case Action.UPDATE_KEY: {
const { key, value } = payload
if (!UPDATABLE_KEYS.some(k => k === key)) {
throw Error(`Unexpected key in LocalStorageContext reducer: '${key}'.`)
} else {
return {
...state,
[key]: value
}
}
}
default: {
throw Error(`Unexpected action type in BalancesContext reducer: '${type}'.`)
}
}
}
const BalancesContext = createContext<[BalancesState, { [k: string]: (...args: any) => void }]>([{}, {}])
function useBalancesContext() {
return useContext(BalancesContext)
}
export default function Provider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(reducer, undefined, initialize)
const startListening = useCallback((chainId, address, tokenAddress) => {
dispatch({ type: Action.START_LISTENING, payload: { chainId, address, tokenAddress } })
}, [])
const stopListening = useCallback((chainId, address, tokenAddress) => {
dispatch({ type: Action.STOP_LISTENING, payload: { chainId, address, tokenAddress } })
}, [])
const update = useCallback((chainId, address, tokenAddress, value, blockNumber) => {
dispatch({ type: Action.UPDATE, payload: { chainId, address, tokenAddress, value, blockNumber } })
}, [])
const batchUpdateAccount = useCallback((chainId, address, tokenAddresses, values, blockNumber) => {
dispatch({ type: Action.BATCH_UPDATE_ACCOUNT, payload: { chainId, address, tokenAddresses, values, blockNumber } })
}, [])
const batchUpdateExchanges = useCallback((chainId, exchangeAddresses, tokenAddresses, values, blockNumber) => {
dispatch({
type: Action.BATCH_UPDATE_EXCHANGES,
payload: { chainId, exchangeAddresses, tokenAddresses, values, blockNumber }
})
}, [])
const updateKey = useCallback((key, value) => {
dispatch({ type: Action.UPDATE_KEY, payload: { key, value } })
}, [])
return (
<BalancesContext.Provider
value={useMemo(
() => [state, { startListening, stopListening, update, batchUpdateAccount, batchUpdateExchanges, updateKey }],
[state, startListening, stopListening, update, batchUpdateAccount, batchUpdateExchanges, updateKey]
)}
>
{children}
</BalancesContext.Provider>
)
}
export function Updater() {
const { chainId, account, library } = useWeb3React()
const blockNumber = useBlockNumber()
const [state, { update, batchUpdateAccount, batchUpdateExchanges }] = useBalancesContext()
// debounce state a little bit to prevent useEffect craziness
const debouncedState = useDebounce(state, 1000)
// cache this debounced state in localstorage
useEffect(() => {
window.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(debouncedState))
}, [debouncedState])
// (slightly janky) balances-wide cache to prevent double/triple/etc. fetching
const fetchedAsOfCache = useRef<{
[chainId: number]: {
[address: string]: {
[tokenAddress: string]: number
}
}
}>({})
// generic balances fetcher abstracting away difference between fetching ETH + token balances
const fetchBalance = useCallback(
(address: string, tokenAddress: string) =>
(tokenAddress === 'ETH' ? getEtherBalance(address, library) : getTokenBalance(tokenAddress, address, library))
.then(value => {
return value.toString()
})
.catch(() => {
return null
}),
[library]
)
// ensure that all balances with >=1 listeners are updated every block
useEffect(() => {
if (typeof chainId === 'number' && typeof blockNumber === 'number') {
for (const address of Object.keys(debouncedState?.[chainId] ?? {})) {
for (const tokenAddress of Object.keys(debouncedState?.[chainId][address])) {
const active = debouncedState[chainId][address][tokenAddress].listenerCount > 0
if (active) {
const cachedFetchedAsOf = fetchedAsOfCache.current?.[chainId]?.[address]?.[tokenAddress]
const fetchedAsOf = debouncedState[chainId][address][tokenAddress]?.blockNumber ?? cachedFetchedAsOf
if (fetchedAsOf !== blockNumber) {
// fetch the balance...
fetchBalance(address, tokenAddress).then(value => {
update(chainId, address, tokenAddress, value, blockNumber)
})
// ...and cache the fetch
fetchedAsOfCache.current = {
...fetchedAsOfCache.current,
[chainId]: {
...fetchedAsOfCache.current?.[chainId],
[address]: {
...fetchedAsOfCache.current?.[chainId]?.[address],
[tokenAddress]: blockNumber
}
}
}
}
}
}
}
}
}, [chainId, blockNumber, debouncedState, fetchBalance, update])
// get a state ref for batch updates
const stateRef = useRef(state)
useEffect(() => {
stateRef.current = state
}, [state])
const allTokenDetails = useAllTokens()
// ensure that we have the user balances for all tokens
const allTokens = useMemo(() => Object.keys(allTokenDetails), [allTokenDetails])
useEffect(() => {
if (typeof chainId === 'number' && typeof account === 'string' && typeof blockNumber === 'number') {
Promise.all(
allTokens
.filter(tokenAddress => {
const hasValue = !!stateRef.current?.[chainId]?.[account]?.[tokenAddress]?.value
const cachedFetchedAsOf = fetchedAsOfCache.current?.[chainId]?.[account]?.[tokenAddress]
const fetchedAsOf = stateRef.current?.[chainId]?.[account][tokenAddress]?.blockNumber ?? cachedFetchedAsOf
// if there's no value, and it's not being fetched, we need to fetch!
if (!hasValue && typeof cachedFetchedAsOf !== 'number') {
return true
// else, if there's a value, check if it's stale
} else if (hasValue) {
const blocksElapsedSinceLastCheck = blockNumber - fetchedAsOf
const stale =
blocksElapsedSinceLastCheck >=
(stateRef.current[chainId][account][tokenAddress].value === '0'
? LONG_BLOCK_TIMEOUT
: SHORT_BLOCK_TIMEOUT)
return stale
} else {
return false
}
})
.map(async tokenAddress => {
fetchedAsOfCache.current = {
...fetchedAsOfCache.current,
[chainId]: {
...fetchedAsOfCache.current?.[chainId],
[account]: {
...fetchedAsOfCache.current?.[chainId]?.[account],
[tokenAddress]: blockNumber
}
}
}
return fetchBalance(account, tokenAddress).then(value => ({ tokenAddress, value }))
})
).then(results => {
batchUpdateAccount(
chainId,
account,
results.map(result => result.tokenAddress),
results.map(result => result.value),
blockNumber
)
})
}
}, [chainId, account, blockNumber, allTokens, fetchBalance, batchUpdateAccount])
// ensure token balances for all exchanges
const allExchangeDetails = useAllExchanges()
// format so we can index by exchange and only update specifc values
const allExchanges = useMemo(() => {
const formattedExchanges = {}
Object.keys(allExchangeDetails).map(token0Address => {
return Object.keys(allExchangeDetails[token0Address]).map(token1Address => {
const exchangeAddress = allExchangeDetails[token0Address][token1Address]
return (formattedExchanges[exchangeAddress] = {
token0: token0Address,
token1: token1Address
})
})
})
return formattedExchanges
}, [allExchangeDetails])
useEffect(() => {
if (typeof chainId === 'number' && typeof blockNumber === 'number') {
Promise.all(
Object.keys(allExchanges)
.filter(exchangeAddress => {
const token0 = allExchanges[exchangeAddress].token0
const token1 = allExchanges[exchangeAddress].token1
const hasValueToken0 = !!stateRef.current?.[chainId]?.[exchangeAddress]?.[token0]?.value
const hasValueToken1 = !!stateRef.current?.[chainId]?.[exchangeAddress]?.[token1]?.value
const cachedFetchedAsOfToken0 = fetchedAsOfCache.current?.[chainId]?.[exchangeAddress]?.token0
const cachedFetchedAsOfToken1 = fetchedAsOfCache.current?.[chainId]?.[exchangeAddress]?.token1
const fetchedAsOfToken0 =
stateRef.current?.[chainId]?.[exchangeAddress]?.[token0]?.blockNumber ?? cachedFetchedAsOfToken0
const fetchedAsOfToken1 =
stateRef.current?.[chainId]?.[exchangeAddress]?.[token1]?.blockNumber ?? cachedFetchedAsOfToken1
// if there's no values, and they're not being fetched, we need to fetch!
if (
(!hasValueToken0 || !hasValueToken1) &&
(typeof cachedFetchedAsOfToken0 !== 'number' || typeof cachedFetchedAsOfToken1 !== 'number')
) {
return true
// else, if there are values, check if they's stale
} else if (hasValueToken0 && hasValueToken0) {
const blocksElapsedSinceLastCheckToken0 = blockNumber - fetchedAsOfToken0
const blocksElapsedSinceLastCheckToken1 = blockNumber - fetchedAsOfToken1
const stale =
fetchedAsOfToken0 !== fetchedAsOfToken1 ||
blocksElapsedSinceLastCheckToken0 >= EXCHANGES_BLOCK_TIMEOUT ||
blocksElapsedSinceLastCheckToken1 >= EXCHANGES_BLOCK_TIMEOUT
return stale
} else {
return false
}
})
.map(async exchangeAddress => {
const token0 = allExchanges[exchangeAddress].token0
const token1 = allExchanges[exchangeAddress].token1
fetchedAsOfCache.current = {
...fetchedAsOfCache.current,
[chainId]: {
...fetchedAsOfCache.current?.[chainId],
[exchangeAddress]: {
...fetchedAsOfCache.current?.[chainId]?.[exchangeAddress],
[token0]: blockNumber,
[token1]: blockNumber
}
}
}
return Promise.all([
fetchBalance(exchangeAddress, token0),
fetchBalance(exchangeAddress, token1)
]).then(([valueToken0, valueToken1]) => ({ exchangeAddress, token0, token1, valueToken0, valueToken1 }))
})
).then(results => {
batchUpdateExchanges(
chainId,
results.flatMap(result => [result.exchangeAddress, result.exchangeAddress]),
results.flatMap(result => [result.token0, result.token1]),
results.flatMap(result => [result.valueToken0, result.valueToken1]),
blockNumber
)
})
}
}, [chainId, account, blockNumber, allExchanges, fetchBalance, batchUpdateExchanges])
return null
}
export function useAllBalances(): Array<TokenAmount> {
const { chainId } = useWeb3React()
const [state] = useBalancesContext()
const allTokens = useAllTokens()
const formattedBalances = useMemo(() => {
if (!state?.[chainId]) {
return {}
} else {
let newBalances = {}
Object.keys(state[chainId]).map(address => {
return Object.keys(state[chainId][address]).map(tokenAddress => {
return (newBalances[chainId] = {
...newBalances[chainId],
[address]: {
...newBalances[chainId]?.[address],
[tokenAddress]: new TokenAmount(
// if token not in token list, must be an exchange -
/**
* @TODO
*
* should we live fetch data here if token not in list
*
*/
allTokens && allTokens[tokenAddress] ? allTokens[tokenAddress] : new Token(chainId, tokenAddress, 18),
JSBI.BigInt(state?.[chainId][address][tokenAddress].value)
)
}
})
})
})
return newBalances
}
}, [allTokens, chainId, state])
return useMemo(() => (typeof chainId === 'number' ? formattedBalances?.[chainId] ?? {} : {}), [
chainId,
formattedBalances
])
}
export function useAddressBalance(address: string, token: Token): TokenAmount | undefined | null {
const { chainId } = useWeb3React()
const [state, { startListening, stopListening }] = useBalancesContext()
const allTokens = useAllTokens()
useEffect(() => {
if (typeof chainId === 'number' && isAddress(address) && isAddress(token.address)) {
startListening(chainId, address, token.address)
return () => {
stopListening(chainId, address, token.address)
}
}
}, [chainId, address, token, startListening, stopListening])
const value = typeof chainId === 'number' ? state?.[chainId]?.[address]?.[token.address]?.value : undefined
const formattedValue = value && new TokenAmount(allTokens?.[token.address], value)
return useMemo(() => formattedValue, [formattedValue])
}
export function useExchangeReserves(tokenAddress: string) {
return []
}

117
src/contexts/Exchanges.tsx Normal file

@ -0,0 +1,117 @@
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
import { ChainId, WETH, Token, Exchange } from '@uniswap/sdk'
import { INITIAL_TOKENS_CONTEXT, useToken } from './Tokens'
import { useAddressBalance } from './Balances'
import { useWeb3React } from '../hooks'
const UPDATE = 'UPDATE'
const ALL_EXCHANGES: [Token, Token][] = [
[
INITIAL_TOKENS_CONTEXT[ChainId.RINKEBY][WETH[ChainId.RINKEBY].address],
INITIAL_TOKENS_CONTEXT[ChainId.RINKEBY]['0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735']
]
]
const EXCHANGE_MAP: {
[chainId: number]: { [token0Address: string]: { [token1Address: string]: string } }
} = ALL_EXCHANGES.reduce((exchangeMap, [tokenA, tokenB]) => {
const tokens: [Token, Token] = tokenA?.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA]
// ensure exchanges are unique
if (exchangeMap?.[tokens[0].chainId]?.[tokens[0].address]?.[tokens[1].address] !== undefined)
throw Error(`Duplicate exchange: ${tokenA} ${tokenB}`)
return {
...exchangeMap,
[tokens[0].chainId]: {
...exchangeMap?.[tokens[0].chainId],
[tokens[0].address]: {
...exchangeMap?.[tokens[0].chainId]?.[tokens[0].address],
[tokens[1].address]: Exchange.getAddress(...tokens)
}
}
}
}, {})
const ExchangeContext = createContext([])
function useExchangesContext() {
return useContext(ExchangeContext)
}
function reducer(state, { type, payload }) {
switch (type) {
case UPDATE: {
const { tokens } = payload
const tokensSorted: [Token, Token] = tokens[0].sortsBefore(tokens[1])
? [tokens[0], tokens[1]]
: [tokens[1], tokens[0]]
return {
...state,
[tokensSorted[0].chainId]: {
...state?.[tokensSorted[0].chainId],
[tokensSorted[0].address]: {
...state?.[tokensSorted[0].chainId]?.[tokensSorted[0].address],
[tokensSorted[1].address]: Exchange.getAddress(tokensSorted[0], tokensSorted[1])
}
}
}
}
default: {
throw Error(`Unexpected action type in ExchangesContext reducer: '${type}'.`)
}
}
}
export default function Provider({ children }) {
const [state, dispatch] = useReducer(reducer, EXCHANGE_MAP)
const update = useCallback((chainId, tokens) => {
dispatch({ type: UPDATE, payload: { chainId, tokens } })
}, [])
return (
<ExchangeContext.Provider value={useMemo(() => [state, { update }], [state, update])}>
{children}
</ExchangeContext.Provider>
)
}
export function useExchangeAddress(tokenA?: Token, tokenB?: Token): string | undefined {
const { chainId } = useWeb3React()
const [state, { update }] = useExchangesContext()
const tokens: [Token, Token] = tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA]
const address = state?.[chainId]?.[tokens[0].address]?.[tokens[1].address]
useEffect(() => {
if (address === undefined && tokenA && tokenB) {
const exchangeAddress = Exchange.getAddress(...tokens)
exchangeAddress && update(chainId, tokens)
}
}, [chainId, address, tokenA, tokenB, tokens, update])
return address
}
export function useExchange(tokenA?: Token, tokenB?: Token): Exchange | undefined {
const address = useExchangeAddress(tokenA, tokenB)
const tokenAmountA = useAddressBalance(address, tokenA)
const tokenAmountB = useAddressBalance(address, tokenB)
const exchange = tokenAmountA && tokenAmountB && new Exchange(tokenAmountA, tokenAmountB)
return exchange
}
export function useAllExchanges() {
const { chainId } = useWeb3React()
const [state] = useExchangesContext()
return useMemo(() => {
return state?.[chainId] || {}
}, [state, chainId])
}

@ -1,700 +0,0 @@
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
import { useWeb3React } from '../hooks'
import {
isAddress,
getTokenName,
getTokenSymbol,
getTokenDecimals,
getTokenExchangeAddressFromFactory,
safeAccess
} from '../utils'
const NAME = 'name'
const SYMBOL = 'symbol'
const DECIMALS = 'decimals'
const EXCHANGE_ADDRESS = 'exchangeAddress'
const UPDATE = 'UPDATE'
const ETH = {
ETH: {
[NAME]: 'Ethereum',
[SYMBOL]: 'ETH',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: null
}
}
export const INITIAL_TOKENS_CONTEXT = {
1: {
'0xB6eD7644C69416d67B522e20bC294A9a9B405B31': {
[NAME]: '0xBitcoin Token',
[SYMBOL]: '0xBTC',
[DECIMALS]: 8,
[EXCHANGE_ADDRESS]: '0x701564Aa6E26816147D4fa211a0779F1B774Bb9B'
},
'0xfC1E690f61EFd961294b3e1Ce3313fBD8aa4f85d': {
[NAME]: 'Aave Interest bearing DAI',
[SYMBOL]: 'aDAI',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x7cfab87AaC0899c093235b342AC0e5B1ACF159EB'
},
'0x737F98AC8cA59f2C68aD658E3C3d8C8963E40a4c': {
[NAME]: 'Amon',
[SYMBOL]: 'AMN',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xE6C198d27a5B71144B40cFa2362ae3166728e0C8'
},
'0xD46bA6D942050d489DBd938a2C909A5d5039A161': {
[NAME]: 'Ampleforth',
[SYMBOL]: 'AMPL',
[DECIMALS]: 9,
[EXCHANGE_ADDRESS]: '0x042dBBDc27F75d277C3D99efE327DB21Bc4fde75'
},
'0xcD62b1C403fa761BAadFC74C525ce2B51780b184': {
[NAME]: 'Aragon Network Juror',
[SYMBOL]: 'ANJ',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x64A9edD3f5fce0252Cd708E26c8dd11205742826'
},
'0x960b236A07cf122663c4303350609A66A7B288C0': {
[NAME]: 'Aragon Network Token',
[SYMBOL]: 'ANT',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x077d52B047735976dfdA76feF74d4d988AC25196'
},
'0x0D8775F648430679A709E98d2b0Cb6250d2887EF': {
[NAME]: 'Basic Attention Token',
[SYMBOL]: 'BAT',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x2E642b8D59B45a1D8c5aEf716A84FF44ea665914'
},
'0x107c4504cd79C5d2696Ea0030a8dD4e92601B82e': {
[NAME]: 'Bloom Token',
[SYMBOL]: 'BLT',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x0E6A53B13688018A3df8C69f99aFB19A3068D04f'
},
'0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C': {
[NAME]: 'Bancor Network Token',
[SYMBOL]: 'BNT',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x87d80DBD37E551F58680B4217b23aF6a752DA83F'
},
'0x26E75307Fc0C021472fEb8F727839531F112f317': {
[NAME]: 'Crypto20',
[SYMBOL]: 'C20',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xF7B5A4b934658025390ff69dB302BC7F2AC4a542'
},
'0x4F9254C83EB525f9FCf346490bbb3ed28a81C667': {
[NAME]: 'CelerToken',
[SYMBOL]: 'CELR',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x1e3740A030AF8c755c888a0ee83aC9E79e09f4F1'
},
'0xF5DCe57282A584D2746FaF1593d3121Fcac444dC': {
[NAME]: 'Compound Dai',
[SYMBOL]: 'cSAI',
[DECIMALS]: 8,
[EXCHANGE_ADDRESS]: '0x45A2FDfED7F7a2c791fb1bdF6075b83faD821ddE'
},
'0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643': {
[NAME]: 'Compound Dai',
[SYMBOL]: 'cDAI',
[DECIMALS]: 8,
[EXCHANGE_ADDRESS]: '0x34E89740adF97C3A9D3f63Cc2cE4a914382c230b'
},
'0x06AF07097C9Eeb7fD685c692751D5C66dB49c215': {
[NAME]: 'Chai',
[SYMBOL]: 'CHAI',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x6C3942B383bc3d0efd3F36eFa1CBE7C8E12C8A2B'
},
'0x41e5560054824eA6B0732E656E3Ad64E20e94E45': {
[NAME]: 'Civic',
[SYMBOL]: 'CVC',
[DECIMALS]: 8,
[EXCHANGE_ADDRESS]: '0x1C6c712b1F4a7c263B1DBd8F97fb447c945d3b9a'
},
'0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359': {
[NAME]: 'Dai Stablecoin v1.0 (SAI)',
[SYMBOL]: 'SAI',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x09cabEC1eAd1c0Ba254B09efb3EE13841712bE14'
},
'0x6B175474E89094C44Da98b954EedeAC495271d0F': {
[NAME]: 'Dai Stablecoin',
[SYMBOL]: 'DAI',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x2a1530C4C41db0B0b2bB646CB5Eb1A67b7158667'
},
'0x0Cf0Ee63788A0849fE5297F3407f701E122cC023': {
[NAME]: 'Streamr DATAcoin',
[SYMBOL]: 'DATA',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x4F0d6E2179938828CfF93dA40a8BA1Df7519Ca8C'
},
'0xE0B7927c4aF23765Cb51314A0E0521A9645F0E2A': {
[NAME]: 'DigixDAO',
[SYMBOL]: 'DGD',
[DECIMALS]: 9,
[EXCHANGE_ADDRESS]: '0xD55C1cA9F5992A2e5E379DCe49Abf24294ABe055'
},
'0x4f3AfEC4E5a3F2A6a1A411DEF7D7dFe50eE057bF': {
[NAME]: 'Digix Gold Token',
[SYMBOL]: 'DGX',
[DECIMALS]: 9,
[EXCHANGE_ADDRESS]: '0xb92dE8B30584392Af27726D5ce04Ef3c4e5c9924'
},
'0xc719d010B63E5bbF2C0551872CD5316ED26AcD83': {
[NAME]: 'Decentralized Insurance Protocol',
[SYMBOL]: 'DIP',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x61792F290e5100FBBcBb2309F03A1Bab869fb850'
},
'0xC0F9bD5Fa5698B6505F643900FFA515Ea5dF54A9': {
[NAME]: 'Donut',
[SYMBOL]: 'DONUT',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xD552119eD44EC8Fa8f87c568769C67Bd02B5b3FB'
},
'0xF629cBd94d3791C9250152BD8dfBDF380E2a3B9c': {
[NAME]: 'Enjin Coin',
[SYMBOL]: 'ENJ',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xb99A23b1a4585fc56d0EC3B76528C27cAd427473'
},
'0x06f65b8CfCb13a9FE37d836fE9708dA38Ecb29B2': {
[NAME]: 'SAINT FAME: Genesis Shirt',
[SYMBOL]: 'FAME',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x5e7907aC70b9a781365c72F2acEE96710bdA042e'
},
'0x4946Fcea7C692606e8908002e55A582af44AC121': {
[NAME]: 'FOAM Token',
[SYMBOL]: 'FOAM',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xf79cb3BEA83BD502737586A6E8B133c378FD1fF2'
},
'0x419D0d8BdD9aF5e606Ae2232ed285Aff190E711b': {
[NAME]: 'FunFair',
[SYMBOL]: 'FUN',
[DECIMALS]: 8,
[EXCHANGE_ADDRESS]: '0x60a87cC7Fca7E53867facB79DA73181B1bB4238B'
},
'0x543Ff227F64Aa17eA132Bf9886cAb5DB55DCAddf': {
[NAME]: 'DAOstack',
[SYMBOL]: 'GEN',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x26Cc0EAb6Cb650B0Db4D0d0dA8cB5BF69F4ad692'
},
'0x6810e776880C02933D47DB1b9fc05908e5386b96': {
[NAME]: 'Gnosis Token',
[SYMBOL]: 'GNO',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xe8e45431b93215566BA923a7E611B7342Ea954DF'
},
'0x12B19D3e2ccc14Da04FAe33e63652ce469b3F2FD': {
[NAME]: 'GRID Token',
[SYMBOL]: 'GRID',
[DECIMALS]: 12,
[EXCHANGE_ADDRESS]: '0x4B17685b330307C751B47f33890c8398dF4Fe407'
},
'0x0000000000b3F879cb30FE243b4Dfee438691c04': {
[NAME]: 'Gastoken.io',
[SYMBOL]: 'GST2',
[DECIMALS]: 2,
[EXCHANGE_ADDRESS]: '0x929507CD3D90Ab11eC4822E9eB5A48eb3a178F19'
},
'0x493C57C4763932315A328269E1ADaD09653B9081': {
[NAME]: 'Fulcrum DAI iToken ',
[SYMBOL]: 'iDAI',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x3E0349F5D38414008B9Bb1907ea422739BE7CD4C'
},
'0x14094949152EDDBFcd073717200DA82fEd8dC960': {
[NAME]: 'Fulcrum SAI iToken ',
[SYMBOL]: 'iSAI',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x81eeD7F1EcbD7FA9978fcc7584296Fb0C215Dc5C'
},
'0x3212b29E33587A00FB1C83346f5dBFA69A458923': {
[NAME]: 'The Tokenized Bitcoin',
[SYMBOL]: 'imBTC',
[DECIMALS]: 8,
[EXCHANGE_ADDRESS]: '0xFFcf45b540e6C9F094Ae656D2e34aD11cdfdb187'
},
'0x6fB3e0A217407EFFf7Ca062D46c26E5d60a14d69': {
[NAME]: 'IoTeX Network',
[SYMBOL]: 'IOTX',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x084f002671a5f03D5498B1e5fb15fc0cfee9a470'
},
'0x818Fc6C2Ec5986bc6E2CBf00939d90556aB12ce5': {
[NAME]: 'Kin',
[SYMBOL]: 'KIN',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xb7520a5F8c832c573d6BD0Df955fC5c9b72400F7'
},
'0xdd974D5C2e2928deA5F71b9825b8b646686BD200': {
[NAME]: 'Kyber Network Crystal',
[SYMBOL]: 'KNC',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x49c4f9bc14884f6210F28342ceD592A633801a8b'
},
'0x514910771AF9Ca656af840dff83E8264EcF986CA': {
[NAME]: 'ChainLink Token',
[SYMBOL]: 'LINK',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xF173214C720f58E03e194085B1DB28B50aCDeeaD'
},
'0x6c6EE5e31d828De241282B9606C8e98Ea48526E2': {
[NAME]: 'HoloToken',
[SYMBOL]: 'HOT',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xd4777E164c6C683E10593E08760B803D58529a8E'
},
'0xBBbbCA6A901c926F240b89EacB641d8Aec7AEafD': {
[NAME]: 'LoopringCoin V2',
[SYMBOL]: 'LRC',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xA539BAaa3aCA455c986bB1E25301CEF936CE1B65'
},
'0x80fB784B7eD66730e8b1DBd9820aFD29931aab03': {
[NAME]: 'EthLend Token',
[SYMBOL]: 'LEND',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xcaA7e4656f6A2B59f5f99c745F91AB26D1210DCe'
},
'0xA4e8C3Ec456107eA67d3075bF9e3DF3A75823DB0': {
[NAME]: 'LoomToken',
[SYMBOL]: 'LOOM',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x417CB32bc991fBbDCaE230C7c4771CC0D69daA6b'
},
'0x58b6A8A3302369DAEc383334672404Ee733aB239': {
[NAME]: 'Livepeer Token',
[SYMBOL]: 'LPT',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xc4a1C45D5546029Fd57128483aE65b56124BFA6A'
},
'0xD29F0b5b3F50b07Fe9a9511F7d86F4f4bAc3f8c4': {
[NAME]: 'Liquidity.Network Token',
[SYMBOL]: 'LQD',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xe3406e7D0155E0a83236eC25D34Cd3D903036669'
},
'0x0F5D2fB29fb7d3CFeE444a200298f468908cC942': {
[NAME]: 'Decentraland MANA',
[SYMBOL]: 'MANA',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xC6581Ce3A005e2801c1e0903281BBd318eC5B5C2'
},
'0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0': {
[NAME]: 'Matic Token',
[SYMBOL]: 'MATIC',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x9a7A75E66B325a3BD46973B2b57c9b8d9D26a621'
},
'0x8888889213DD4dA823EbDD1e235b09590633C150': {
[NAME]: 'Marblecoin',
[SYMBOL]: 'MBC',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xE1b7AeC3639068b474bFbcB916580fc28A20717B'
},
'0x80f222a749a2e18Eb7f676D371F19ad7EFEEe3b7': {
[NAME]: 'Magnolia Token',
[SYMBOL]: 'MGN',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xdd80Ca8062c7Ef90FcA2547E6a2A126C596e611F'
},
'0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2': {
[NAME]: 'Maker',
[SYMBOL]: 'MKR',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x2C4Bd064b998838076fa341A83d007FC2FA50957'
},
'0xec67005c4E498Ec7f55E092bd1d35cbC47C91892': {
[NAME]: 'Melon Token',
[SYMBOL]: 'MLN',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xA931F4eB165AC307fD7431b5EC6eADde53E14b0C'
},
'0x957c30aB0426e0C93CD8241E2c60392d08c6aC8e': {
[NAME]: 'Modum Token',
[SYMBOL]: 'MOD',
[DECIMALS]: 0,
[EXCHANGE_ADDRESS]: '0xCCB98654CD486216fFF273dd025246588E77cFC1'
},
'0xB62132e35a6c13ee1EE0f84dC5d40bad8d815206': {
[NAME]: 'Nexo',
[SYMBOL]: 'NEXO',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x069C97DBA948175D10af4b2414969e0B88d44669'
},
'0x1776e1F26f98b1A5dF9cD347953a26dd3Cb46671': {
[NAME]: 'Numeraire',
[SYMBOL]: 'NMR',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x2Bf5A5bA29E60682fC56B2Fcf9cE07Bef4F6196f'
},
'0x4575f41308EC1483f3d399aa9a2826d74Da13Deb': {
[NAME]: 'Orchid',
[SYMBOL]: 'OXT',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xe9a5bbe41dc63D555E06746b047d624E3343EA52'
},
'0xD56daC73A4d6766464b38ec6D91eB45Ce7457c44': {
[NAME]: 'Panvala pan',
[SYMBOL]: 'PAN',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xF53bBFBff01c50F2D42D542b09637DcA97935fF7'
},
'0x8E870D67F660D95d5be530380D0eC0bd388289E1': {
[NAME]: 'PAX',
[SYMBOL]: 'PAX',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xC040d51b07Aea5d94a89Bc21E8078B77366Fc6C7'
},
'0x45804880De22913dAFE09f4980848ECE6EcbAf78': {
[NAME]: 'Paxos Gold',
[SYMBOL]: 'PAXG',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x0d2E1a84638bD1B6c0C260c758c39451D4587be1'
},
'0x93ED3FBe21207Ec2E8f2d3c3de6e058Cb73Bc04d': {
[NAME]: 'Pinakion',
[SYMBOL]: 'PNK',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xF506828B166de88cA2EDb2A98D960aBba0D2402A'
},
'0x6758B7d441a9739b98552B373703d8d3d14f9e62': {
[NAME]: 'POA ERC20 on Foundation',
[SYMBOL]: 'POA20',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xA2E6B3EF205FeAEe475937c4883b24E6eB717eeF'
},
'0x687BfC3E73f6af55F0CccA8450114D107E781a0e': {
[NAME]: 'QChi',
[SYMBOL]: 'QCH',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x755899F0540c3548b99E68C59AdB0f15d2695188'
},
'0x99ea4dB9EE77ACD40B119BD1dC4E33e1C070b80d': {
[NAME]: 'Quantstamp Token',
[SYMBOL]: 'QSP',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x82Db9FC4956Fa40efe1e35d881004612B5CB2cc2'
},
'0xF970b8E36e23F7fC3FD752EeA86f8Be8D83375A6': {
[NAME]: 'Ripio Credit Network Token',
[SYMBOL]: 'RCN',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xD91FF16Ef92568fC27F466C3c5613e43313Ab1dc'
},
'0x255Aa6DF07540Cb5d3d297f0D0D4D84cb52bc8e6': {
[NAME]: 'Raiden Token',
[SYMBOL]: 'RDN',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x7D03CeCb36820b4666F45E1b4cA2538724Db271C'
},
'0x408e41876cCCDC0F92210600ef50372656052a38': {
[NAME]: 'Republic Token',
[SYMBOL]: 'REN',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x43892992B0b102459E895B88601Bb2C76736942c'
},
'0x1985365e9f78359a9B6AD760e32412f4a445E862': {
[NAME]: 'Reputation',
[SYMBOL]: 'REP',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x48B04d2A05B6B604d8d5223Fd1984f191DED51af'
},
'0x9469D013805bFfB7D3DEBe5E7839237e535ec483': {
[NAME]: 'Darwinia Network Native Token',
[SYMBOL]: 'RING',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xeBD8AA50b26bFa63007d61eBa777A9DdE7e43c64'
},
'0x607F4C5BB672230e8672085532f7e901544a7375': {
[NAME]: 'iEx.ec Network Token',
[SYMBOL]: 'RLC',
[DECIMALS]: 9,
[EXCHANGE_ADDRESS]: '0xA825CAE02B310E9901b4776806CE25db520c8642'
},
'0xB4EFd85c19999D84251304bDA99E90B92300Bd93': {
[NAME]: 'Rocket Pool',
[SYMBOL]: 'RPL',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x3Fb2F18065926DdB33E7571475c509541d15dA0e'
},
'0x4156D3342D5c385a87D264F90653733592000581': {
[NAME]: 'Salt',
[SYMBOL]: 'SALT',
[DECIMALS]: 8,
[EXCHANGE_ADDRESS]: '0xC0C59cDe851bfcbdddD3377EC10ea54A18Efb937'
},
'0x7C5A0CE9267ED19B22F8cae653F198e3E8daf098': {
[NAME]: 'SANtiment network token',
[SYMBOL]: 'SAN',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x8a8D7aD4b89D91983cd069C58C4AA9F2f4166298'
},
'0x5e74C9036fb86BD7eCdcb084a0673EFc32eA31cb': {
[NAME]: 'Synth sETH',
[SYMBOL]: 'sETH',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xe9Cf7887b93150D4F2Da7dFc6D502B216438F244'
},
'0x3A9FfF453d50D4Ac52A6890647b823379ba36B9E': {
[NAME]: 'Shuffle.Monster V3',
[SYMBOL]: 'SHUF',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x536956Fab86774fb55CfaAcF496BC25E4d2B435C'
},
'0x744d70FDBE2Ba4CF95131626614a1763DF805B9E': {
[NAME]: 'Status Network Token',
[SYMBOL]: 'SNT',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x1aEC8F11A7E78dC22477e91Ed924Fab46e3A88Fd'
},
'0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F': {
[NAME]: 'Synthetix Network Token',
[SYMBOL]: 'SNX',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x3958B4eC427F8fa24eB60F42821760e88d485f7F'
},
'0x23B608675a2B2fB1890d3ABBd85c5775c51691d5': {
[NAME]: 'Unisocks Edition 0',
[SYMBOL]: 'SOCKS',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x22d8432cc7aA4f8712a655fC4cdfB1baEC29FCA9'
},
'0x42d6622deCe394b54999Fbd73D108123806f6a18': {
[NAME]: 'SPANK',
[SYMBOL]: 'SPANK',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x4e395304655F0796bc3bc63709DB72173b9DdF98'
},
'0xB64ef51C888972c908CFacf59B47C1AfBC0Ab8aC': {
[NAME]: 'StorjToken',
[SYMBOL]: 'STORJ',
[DECIMALS]: 8,
[EXCHANGE_ADDRESS]: '0xA7298541E52f96d42382eCBe4f242cBcBC534d02'
},
'0x57Ab1ec28D129707052df4dF418D58a2D46d5f51': {
[NAME]: 'Synth sUSD',
[SYMBOL]: 'sUSD',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xB944d13b2f4047fc7bd3F7013bcf01b115fb260d'
},
'0x00006100F7090010005F1bd7aE6122c3C2CF0090': {
[NAME]: 'TrueAUD',
[SYMBOL]: 'TAUD',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x88dF13889E20EFa93Ff9a0C08f101F431bD9DDD7'
},
'0x00000100F2A2bd000715001920eB70D229700085': {
[NAME]: 'TrueCAD',
[SYMBOL]: 'TCAD',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xF996D7d9BaCb9217ca64BBce1b1cD72E0E886Be6'
},
'0x00000000441378008EA67F4284A57932B1c000a5': {
[NAME]: 'TrueGBP',
[SYMBOL]: 'TGBP',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x6bFa119a191576Ba26Bc5e711432aCA0cFda04DE'
},
'0x0000852600CEB001E08e00bC008be620d60031F2': {
[NAME]: 'TrueHKD',
[SYMBOL]: 'THKD',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x505C02B4aa1286375FBDF0c390AC0fe9209DCB05'
},
'0xaAAf91D9b90dF800Df4F55c205fd6989c977E73a': {
[NAME]: 'Monolith TKN',
[SYMBOL]: 'TKN',
[DECIMALS]: 8,
[EXCHANGE_ADDRESS]: '0xb6cFBf322db47D39331E306005DC7E5e6549942B'
},
'0xCb94be6f13A1182E4A4B6140cb7bf2025d28e41B': {
[NAME]: 'Trustcoin',
[SYMBOL]: 'TRST',
[DECIMALS]: 6,
[EXCHANGE_ADDRESS]: '0x95E4649F5209dD292cAF1F087b8F1Db3bE24927f'
},
'0x2C537E5624e4af88A7ae4060C022609376C8D0EB': {
[NAME]: 'BiLira',
[SYMBOL]: 'TRYB',
[DECIMALS]: 6,
[EXCHANGE_ADDRESS]: '0x122327Fd43B2C66DD9e4B6c91c8f071E217558eF'
},
'0x0000000000085d4780B73119b644AE5ecd22b376': {
[NAME]: 'TrueUSD',
[SYMBOL]: 'TUSD',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x5048b9d01097498Fd72F3F14bC9Bc74A5aAc8fA7'
},
'0x09cabEC1eAd1c0Ba254B09efb3EE13841712bE14': {
[NAME]: 'Uniswap V1',
[SYMBOL]: 'UNI-V1:SAI',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x601c32E0580D3aef9437dB52D09f5a5D7E60eC22'
},
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48': {
[NAME]: 'USD//C',
[SYMBOL]: 'USDC',
[DECIMALS]: 6,
[EXCHANGE_ADDRESS]: '0x97deC872013f6B5fB443861090ad931542878126'
},
'0xA4Bdb11dc0a2bEC88d24A3aa1E6Bb17201112eBe': {
[NAME]: 'StableUSD',
[SYMBOL]: 'USDS',
[DECIMALS]: 6,
[EXCHANGE_ADDRESS]: '0x7Ef7191AB91dDB4D7cC347fbFA170355acbaf02D'
},
'0x8f3470A7388c05eE4e7AF3d01D8C722b0FF52374': {
[NAME]: 'Veritaseum',
[SYMBOL]: 'VERI',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x17e5BF07D696eaf0d14caA4B44ff8A1E17B34de3'
},
'0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599': {
[NAME]: 'Wrapped BTC',
[SYMBOL]: 'WBTC',
[DECIMALS]: 8,
[EXCHANGE_ADDRESS]: '0x4d2f5cFbA55AE412221182D8475bC85799A5644b'
},
'0x09fE5f0236F0Ea5D930197DCE254d77B04128075': {
[NAME]: 'Wrapped CryptoKitties',
[SYMBOL]: 'WCK',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x4FF7Fa493559c40aBd6D157a0bfC35Df68d8D0aC'
},
'0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2': {
[NAME]: 'Wrapped Ether',
[SYMBOL]: 'WETH',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xA2881A90Bf33F03E7a3f803765Cd2ED5c8928dFb'
},
'0xB4272071eCAdd69d933AdcD19cA99fe80664fc08': {
[NAME]: 'CryptoFranc',
[SYMBOL]: 'XCHF',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x8dE0d002DC83478f479dC31F76cB0a8aa7CcEa17'
},
'0x0f7F961648aE6Db43C75663aC7E5414Eb79b5704': {
[NAME]: 'XIO Network',
[SYMBOL]: 'XIO',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x7B6E5278a14d5318571d65aceD036d09c998C707'
},
'0xE41d2489571d322189246DaFA5ebDe1F4699F498': {
[NAME]: '0x Protocol Token',
[SYMBOL]: 'ZRX',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xaE76c84C9262Cdb9abc0C2c8888e62Db8E22A0bF'
}
},
4: {
'0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa': {
[NAME]: 'Dai',
[SYMBOL]: 'DAI',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xaF51BaAA766b65E8B3Ee0C2c33186325ED01eBD5'
}
}
}
const TokensContext = createContext()
function useTokensContext() {
return useContext(TokensContext)
}
function reducer(state, { type, payload }) {
switch (type) {
case UPDATE: {
const { networkId, tokenAddress, name, symbol, decimals, exchangeAddress } = payload
return {
...state,
[networkId]: {
...(safeAccess(state, [networkId]) || {}),
[tokenAddress]: {
[NAME]: name,
[SYMBOL]: symbol,
[DECIMALS]: decimals,
[EXCHANGE_ADDRESS]: exchangeAddress
}
}
}
}
default: {
throw Error(`Unexpected action type in TokensContext reducer: '${type}'.`)
}
}
}
export default function Provider({ children }) {
const [state, dispatch] = useReducer(reducer, INITIAL_TOKENS_CONTEXT)
const update = useCallback((networkId, tokenAddress, name, symbol, decimals, exchangeAddress) => {
dispatch({ type: UPDATE, payload: { networkId, tokenAddress, name, symbol, decimals, exchangeAddress } })
}, [])
return (
<TokensContext.Provider value={useMemo(() => [state, { update }], [state, update])}>
{children}
</TokensContext.Provider>
)
}
export function useTokenDetails(tokenAddress) {
const { library, chainId } = useWeb3React()
const [state, { update }] = useTokensContext()
const allTokensInNetwork = { ...ETH, ...(safeAccess(state, [chainId]) || {}) }
const { [NAME]: name, [SYMBOL]: symbol, [DECIMALS]: decimals, [EXCHANGE_ADDRESS]: exchangeAddress } =
safeAccess(allTokensInNetwork, [tokenAddress]) || {}
useEffect(() => {
if (
isAddress(tokenAddress) &&
(name === undefined || symbol === undefined || decimals === undefined || exchangeAddress === undefined) &&
(chainId || chainId === 0) &&
library
) {
let stale = false
const namePromise = getTokenName(tokenAddress, library).catch(() => null)
const symbolPromise = getTokenSymbol(tokenAddress, library).catch(() => null)
const decimalsPromise = getTokenDecimals(tokenAddress, library).catch(() => null)
const exchangeAddressPromise = getTokenExchangeAddressFromFactory(tokenAddress, chainId, library).catch(
() => null
)
Promise.all([namePromise, symbolPromise, decimalsPromise, exchangeAddressPromise]).then(
([resolvedName, resolvedSymbol, resolvedDecimals, resolvedExchangeAddress]) => {
if (!stale) {
update(chainId, tokenAddress, resolvedName, resolvedSymbol, resolvedDecimals, resolvedExchangeAddress)
}
}
)
return () => {
stale = true
}
}
}, [tokenAddress, name, symbol, decimals, exchangeAddress, chainId, library, update])
return { name, symbol, decimals, exchangeAddress }
}
export function useAllTokenDetails() {
const { chainId } = useWeb3React()
const [state] = useTokensContext()
return useMemo(() => ({ ...ETH, ...(safeAccess(state, [chainId]) || {}) }), [state, chainId])
}

108
src/contexts/Tokens.tsx Normal file

@ -0,0 +1,108 @@
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
import { ChainId, WETH, Token } from '@uniswap/sdk'
import { useWeb3React } from '../hooks'
import { isAddress, getTokenName, getTokenSymbol, getTokenDecimals, safeAccess } from '../utils'
const UPDATE = 'UPDATE'
export const ALL_TOKENS = [
WETH[ChainId.RINKEBY],
new Token(ChainId.RINKEBY, '0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735', 18, 'DAI', 'Dai Stablecoin')
]
// only meant to be used in exchanges.ts!
export const INITIAL_TOKENS_CONTEXT = ALL_TOKENS.reduce((tokenMap, token) => {
// ensure tokens are unique
if (tokenMap?.[token.chainId]?.[token.address] !== undefined) throw Error(`Duplicate token: ${token}`)
return {
...tokenMap,
[token.chainId]: {
...tokenMap?.[token.chainId],
[token.address]: token
}
}
}, {})
const TokensContext = createContext([])
function useTokensContext() {
return useContext(TokensContext)
}
function reducer(state, { type, payload }) {
switch (type) {
case UPDATE: {
const { chainId, token } = payload
return {
...state,
[chainId]: {
...(state?.[chainId] || {}),
[token.address]: token
}
}
}
default: {
throw Error(`Unexpected action type in TokensContext reducer: '${type}'.`)
}
}
}
export default function Provider({ children }) {
const [state, dispatch] = useReducer(reducer, INITIAL_TOKENS_CONTEXT)
const update = useCallback((chainId, token) => {
dispatch({ type: UPDATE, payload: { chainId, token } })
}, [])
return (
<TokensContext.Provider value={useMemo(() => [state, { update }], [state, update])}>
{children}
</TokensContext.Provider>
)
}
export function useToken(tokenAddress: string): Token {
const { library, chainId } = useWeb3React()
const [state, { update }] = useTokensContext()
const allTokensInNetwork = state?.[chainId] || {}
const token = safeAccess(allTokensInNetwork, [tokenAddress]) || {}
useEffect(() => {
if (
isAddress(tokenAddress) &&
(token === undefined || token.name === undefined || token.symbol === undefined || token.decimals === undefined) &&
(chainId || chainId === 0) &&
library
) {
let stale = false
const namePromise = getTokenName(tokenAddress, library).catch(() => null)
const symbolPromise = getTokenSymbol(tokenAddress, library).catch(() => null)
const decimalsPromise = getTokenDecimals(tokenAddress, library).catch(() => null)
Promise.all([namePromise, symbolPromise, decimalsPromise]).then(
([resolvedName, resolvedSymbol, resolvedDecimals]) => {
if (!stale && resolvedDecimals) {
const newToken: Token = new Token(chainId, tokenAddress, resolvedDecimals, resolvedSymbol, resolvedName)
update(chainId, newToken)
}
}
)
return () => {
stale = true
}
}
}, [tokenAddress, token, chainId, library, update])
return token
}
export function useAllTokens() {
const { chainId } = useWeb3React()
const [state] = useTokensContext()
return useMemo(() => {
return state?.[chainId] || {}
}, [state, chainId])
}

@ -11,6 +11,7 @@ import ApplicationContextProvider, { Updater as ApplicationContextUpdater } from
import TransactionContextProvider, { Updater as TransactionContextUpdater } from './contexts/Transactions'
import BalancesContextProvider, { Updater as BalancesContextUpdater } from './contexts/Balances'
import TokensContextProvider from './contexts/Tokens'
import ExchangesContextProvider from './contexts/Exchanges'
import AllowancesContextProvider from './contexts/Allowances'
import App from './pages/App'
import ThemeProvider, { GlobalStyle } from './theme'
@ -40,11 +41,13 @@ function ContextProviders({ children }) {
<LocalStorageContextProvider>
<ApplicationContextProvider>
<TransactionContextProvider>
<ExchangesContextProvider>
<TokensContextProvider>
<BalancesContextProvider>
<AllowancesContextProvider>{children}</AllowancesContextProvider>
</BalancesContextProvider>
</TokensContextProvider>
</ExchangesContextProvider>
</TransactionContextProvider>
</ApplicationContextProvider>
</LocalStorageContextProvider>

@ -11,7 +11,9 @@ import { isAddress, getAllQueryParams } from '../utils'
const Swap = lazy(() => import('./Swap'))
const Send = lazy(() => import('./Send'))
const Pool = lazy(() => import('./Pool'))
const Pool = lazy(() => import('./Supply'))
const Add = lazy(() => import('./Supply/AddLiquidity'))
const Remove = lazy(() => import('./Supply/RemoveLiquidity'))
const AppWrapper = styled.div`
display: flex;
@ -42,9 +44,13 @@ const BodyWrapper = styled.div`
`
const Body = styled.div`
max-width: 35rem;
max-width: 28rem;
width: 90%;
/* margin: 0 1.25rem 1.25rem 1.25rem; */
background: ${({ theme }) => theme.panelBackground};
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.04), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
0px 24px 32px rgba(0, 0, 0, 0.04);
border-radius: 20px;
padding: 2rem 1rem;
`
export default function App() {
@ -96,16 +102,27 @@ export default function App() {
}
}}
/>
<Route exaxct path={'/supply'} component={() => <Pool params={params} />} />
<Route
path={[
'/add-liquidity',
'/remove-liquidity',
'/create-exchange',
'/create-exchange/:tokenAddress?'
]}
component={() => <Pool params={params} />}
exact
strict
path={'/add/:tokens'}
component={({ match }) => {
const tokens = match.params.tokens.split('-')
let t0
let t1
if (tokens) {
t0 = tokens[0] === 'ETH' ? 'ETH' : isAddress(tokens[0])
t1 = tokens[1] === 'ETH' ? 'ETH' : isAddress(tokens[1])
}
if (t0 && t1) {
return <Add params={params} token0={t0} token1={t1} />
} else {
return <Redirect to={{ pathname: '/supply' }} />
}
}}
/>
<Redirect to="/swap" />
<Route exaxct path={'/remove'} component={() => <Remove params={params} />} />
</Switch>
</Suspense>
</BrowserRouter>

@ -1,722 +0,0 @@
import React, { useReducer, useState, useCallback, useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { createBrowserHistory } from 'history'
import { ethers } from 'ethers'
import ReactGA from 'react-ga'
import styled from 'styled-components'
import { Button } from '../../theme'
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import OversizedPanel from '../../components/OversizedPanel'
import ContextualInfo from '../../components/ContextualInfo'
import { ReactComponent as Plus } from '../../assets/images/plus-blue.svg'
import WarningCard from '../../components/WarningCard'
import { useWeb3React, useExchangeContract } from '../../hooks'
import { brokenTokens } from '../../constants'
import { amountFormatter, calculateGasMargin } from '../../utils'
import { useTransactionAdder } from '../../contexts/Transactions'
import { useTokenDetails, INITIAL_TOKENS_CONTEXT } from '../../contexts/Tokens'
import { useAddressBalance, useExchangeReserves } from '../../contexts/Balances'
import { useAddressAllowance } from '../../contexts/Allowances'
const INPUT = 0
const OUTPUT = 1
// denominated in bips
const ALLOWED_SLIPPAGE = ethers.utils.bigNumberify(200)
// denominated in seconds
const DEADLINE_FROM_NOW = 60 * 15
// denominated in bips
const GAS_MARGIN = ethers.utils.bigNumberify(1000)
const BlueSpan = styled.span`
color: ${({ theme }) => theme.royalBlue};
`
const NewExchangeWarning = styled.div`
margin-top: 1rem;
padding: 1rem;
margin-bottom: 2rem;
border: 1px solid rgba($pizazz-orange, 0.4);
background-color: rgba($pizazz-orange, 0.1);
border-radius: 1rem;
`
const NewExchangeWarningText = styled.div`
font-size: 0.75rem;
line-height: 1rem;
text-align: center;
:first-child {
padding-bottom: 0.3rem;
font-weight: 500;
}
`
const LastSummaryText = styled.div`
margin-top: 1rem;
`
const DownArrowBackground = styled.div`
${({ theme }) => theme.flexRowNoWrap}
justify-content: center;
align-items: center;
`
const SummaryPanel = styled.div`
${({ theme }) => theme.flexColumnNoWrap}
padding: 1rem 0;
`
const ExchangeRateWrapper = styled.div`
${({ theme }) => theme.flexRowNoWrap};
align-items: center;
color: ${({ theme }) => theme.doveGray};
font-size: 0.75rem;
padding: 0.25rem 1rem 0;
`
const ExchangeRate = styled.span`
flex: 1 1 auto;
width: 0;
color: ${({ theme }) => theme.doveGray};
`
const Flex = styled.div`
display: flex;
justify-content: center;
padding: 2rem;
button {
max-width: 20rem;
}
`
const WrappedPlus = ({ isError, highSlippageWarning, ...rest }) => <Plus {...rest} />
const ColoredWrappedPlus = styled(WrappedPlus)`
width: 0.625rem;
height: 0.625rem;
position: relative;
padding: 0.875rem;
path {
stroke: ${({ active, theme }) => (active ? theme.royalBlue : theme.chaliceGray)};
}
`
function calculateSlippageBounds(value) {
if (value) {
const offset = value.mul(ALLOWED_SLIPPAGE).div(ethers.utils.bigNumberify(10000))
const minimum = value.sub(offset)
const maximum = value.add(offset)
return {
minimum: minimum.lt(ethers.constants.Zero) ? ethers.constants.Zero : minimum,
maximum: maximum.gt(ethers.constants.MaxUint256) ? ethers.constants.MaxUint256 : maximum
}
} else {
return {}
}
}
function calculateMaxOutputVal(value) {
if (value) {
return value.mul(ethers.utils.bigNumberify(10000)).div(ALLOWED_SLIPPAGE.add(ethers.utils.bigNumberify(10000)))
}
}
function initialAddLiquidityState(state) {
return {
inputValue: state.ethAmountURL ? state.ethAmountURL : '',
outputValue: state.tokenAmountURL && !state.ethAmountURL ? state.tokenAmountURL : '',
lastEditedField: state.tokenAmountURL && state.ethAmountURL === '' ? OUTPUT : INPUT,
outputCurrency: state.tokenURL ? state.tokenURL : ''
}
}
function addLiquidityStateReducer(state, action) {
switch (action.type) {
case 'SELECT_CURRENCY': {
return {
...state,
outputCurrency: action.payload
}
}
case 'UPDATE_VALUE': {
const { inputValue, outputValue } = state
const { field, value } = action.payload
return {
...state,
inputValue: field === INPUT ? value : inputValue,
outputValue: field === OUTPUT ? value : outputValue,
lastEditedField: field
}
}
case 'UPDATE_DEPENDENT_VALUE': {
const { inputValue, outputValue } = state
const { field, value } = action.payload
return {
...state,
inputValue: field === INPUT ? value : inputValue,
outputValue: field === OUTPUT ? value : outputValue
}
}
default: {
return initialAddLiquidityState()
}
}
}
function getExchangeRate(inputValue, inputDecimals, outputValue, outputDecimals, invert = false) {
try {
if (
inputValue &&
(inputDecimals || inputDecimals === 0) &&
outputValue &&
(outputDecimals || outputDecimals === 0)
) {
const factor = ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18))
if (invert) {
return inputValue
.mul(factor)
.mul(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(outputDecimals)))
.div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(inputDecimals)))
.div(outputValue)
} else {
return outputValue
.mul(factor)
.mul(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(inputDecimals)))
.div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(outputDecimals)))
.div(inputValue)
}
}
} catch {}
}
function getMarketRate(reserveETH, reserveToken, decimals, invert = false) {
return getExchangeRate(reserveETH, 18, reserveToken, decimals, invert)
}
export default function AddLiquidity({ params }) {
const { t } = useTranslation()
const { library, account, active, chainId } = useWeb3React()
const urlAddedTokens = {}
if (params.token) {
urlAddedTokens[params.token] = true
}
// clear url of query
useEffect(() => {
const history = createBrowserHistory()
history.push(window.location.pathname + '')
}, [])
const [addLiquidityState, dispatchAddLiquidityState] = useReducer(
addLiquidityStateReducer,
{ ethAmountURL: params.ethAmount, tokenAmountURL: params.tokenAmount, tokenURL: params.token },
initialAddLiquidityState
)
const { inputValue, outputValue, lastEditedField, outputCurrency } = addLiquidityState
const inputCurrency = 'ETH'
const [inputValueParsed, setInputValueParsed] = useState()
const [outputValueParsed, setOutputValueParsed] = useState()
const [inputError, setInputError] = useState()
const [outputError, setOutputError] = useState()
const [zeroDecimalError, setZeroDecimalError] = useState()
const [brokenTokenWarning, setBrokenTokenWarning] = useState()
const { symbol, decimals, exchangeAddress } = useTokenDetails(outputCurrency)
const exchangeContract = useExchangeContract(exchangeAddress)
const [totalPoolTokens, setTotalPoolTokens] = useState()
const fetchPoolTokens = useCallback(() => {
if (exchangeContract) {
exchangeContract.totalSupply().then(totalSupply => {
setTotalPoolTokens(totalSupply)
})
}
}, [exchangeContract])
useEffect(() => {
fetchPoolTokens()
library.on('block', fetchPoolTokens)
return () => {
library.removeListener('block', fetchPoolTokens)
}
}, [fetchPoolTokens, library])
const poolTokenBalance = useAddressBalance(account, exchangeAddress)
const exchangeETHBalance = useAddressBalance(exchangeAddress, 'ETH')
const exchangeTokenBalance = useAddressBalance(exchangeAddress, outputCurrency)
const { reserveETH, reserveToken } = useExchangeReserves(outputCurrency)
const isNewExchange = !!(reserveETH && reserveToken && reserveETH.isZero() && reserveToken.isZero())
// 18 decimals
const poolTokenPercentage =
poolTokenBalance && totalPoolTokens && isNewExchange === false && !totalPoolTokens.isZero()
? poolTokenBalance.mul(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18))).div(totalPoolTokens)
: undefined
const ethShare =
exchangeETHBalance && poolTokenPercentage
? exchangeETHBalance
.mul(poolTokenPercentage)
.div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18)))
: undefined
const tokenShare =
exchangeTokenBalance && poolTokenPercentage
? exchangeTokenBalance
.mul(poolTokenPercentage)
.div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18)))
: undefined
const liquidityMinted = isNewExchange
? inputValueParsed
: totalPoolTokens && inputValueParsed && exchangeETHBalance && !exchangeETHBalance.isZero()
? totalPoolTokens.mul(inputValueParsed).div(exchangeETHBalance)
: undefined
// user balances
const inputBalance = useAddressBalance(account, inputCurrency)
const outputBalance = useAddressBalance(account, outputCurrency)
const ethPerLiquidityToken =
exchangeETHBalance && totalPoolTokens && isNewExchange === false && !totalPoolTokens.isZero()
? exchangeETHBalance.mul(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18))).div(totalPoolTokens)
: undefined
const tokenPerLiquidityToken =
exchangeTokenBalance && totalPoolTokens && isNewExchange === false && !totalPoolTokens.isZero()
? exchangeTokenBalance.mul(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18))).div(totalPoolTokens)
: undefined
const outputValueMax = outputValueParsed && calculateSlippageBounds(outputValueParsed).maximum
const liquidityTokensMin = liquidityMinted && calculateSlippageBounds(liquidityMinted).minimum
const marketRate = useMemo(() => {
return getMarketRate(reserveETH, reserveToken, decimals)
}, [reserveETH, reserveToken, decimals])
const marketRateInverted = useMemo(() => {
return getMarketRate(reserveETH, reserveToken, decimals, true)
}, [reserveETH, reserveToken, decimals])
function renderTransactionDetails() {
const b = text => <BlueSpan>{text}</BlueSpan>
if (isNewExchange) {
return (
<div>
<div>
{t('youAreAdding')} {b(`${inputValue} ETH`)} {t('and')} {b(`${outputValue} ${symbol}`)} {t('intoPool')}
</div>
<LastSummaryText>
{t('youAreSettingExRate')}{' '}
{b(
`1 ETH = ${amountFormatter(
getMarketRate(inputValueParsed, outputValueParsed, decimals),
18,
4,
false
)} ${symbol}`
)}
.
</LastSummaryText>
<LastSummaryText>
{t('youWillMint')} {b(`${inputValue}`)} {t('liquidityTokens')}
</LastSummaryText>
<LastSummaryText>{t('totalSupplyIs0')}</LastSummaryText>
</div>
)
} else {
return (
<>
<div>
{t('youAreAdding')} {b(`${amountFormatter(inputValueParsed, 18, 4)} ETH`)} {t('and')} {'at most'}{' '}
{b(`${amountFormatter(outputValueMax, decimals, Math.min(decimals, 4))} ${symbol}`)} {t('intoPool')}
</div>
<LastSummaryText>
{t('youWillMint')} {b(amountFormatter(liquidityMinted, 18, 4))} {t('liquidityTokens')}
</LastSummaryText>
<LastSummaryText>
{t('totalSupplyIs')} {b(amountFormatter(totalPoolTokens, 18, 4))}
</LastSummaryText>
<LastSummaryText>
{t('tokenWorth')} {b(amountFormatter(ethPerLiquidityToken, 18, 4))} ETH {t('and')}{' '}
{b(amountFormatter(tokenPerLiquidityToken, decimals, Math.min(decimals, 4)))} {symbol}
</LastSummaryText>
</>
)
}
}
function renderSummary() {
let contextualInfo = ''
let isError = false
if (brokenTokenWarning) {
contextualInfo = t('brokenToken')
isError = true
} else if (zeroDecimalError) {
contextualInfo = zeroDecimalError
} else if (inputError || outputError) {
contextualInfo = inputError || outputError
isError = true
} else if (!inputCurrency || !outputCurrency) {
contextualInfo = t('selectTokenCont')
} else if (!inputValue) {
contextualInfo = t('enterValueCont')
} else if (!account) {
contextualInfo = t('noWallet')
isError = true
}
return (
<ContextualInfo
openDetailsText={t('transactionDetails')}
closeDetailsText={t('hideDetails')}
contextualInfo={contextualInfo}
isError={isError}
renderTransactionDetails={renderTransactionDetails}
/>
)
}
const addTransaction = useTransactionAdder()
async function onAddLiquidity() {
// take ETH amount, multiplied by ETH rate and 2 for total tx size
let ethTransactionSize = (inputValueParsed / 1e18) * 2
const deadline = Math.ceil(Date.now() / 1000) + DEADLINE_FROM_NOW
const estimatedGasLimit = await exchangeContract.estimate.addLiquidity(
isNewExchange ? ethers.constants.Zero : liquidityTokensMin,
isNewExchange ? outputValueParsed : outputValueMax,
deadline,
{
value: inputValueParsed
}
)
const gasLimit = calculateGasMargin(estimatedGasLimit, GAS_MARGIN)
exchangeContract
.addLiquidity(
isNewExchange ? ethers.constants.Zero : liquidityTokensMin,
isNewExchange ? outputValueParsed : outputValueMax,
deadline,
{
value: inputValueParsed,
gasLimit
}
)
.then(response => {
// log pool added to and total usd amount
ReactGA.event({
category: 'Transaction',
action: 'Add Liquidity',
label: outputCurrency,
value: ethTransactionSize,
dimension1: response.hash
})
ReactGA.event({
category: 'Hash',
action: response.hash,
label: ethTransactionSize.toString()
})
addTransaction(response)
})
}
function formatBalance(value) {
return `Balance: ${value}`
}
useEffect(() => {
setBrokenTokenWarning(false)
for (let i = 0; i < brokenTokens.length; i++) {
if (brokenTokens[i].toLowerCase() === outputCurrency.toLowerCase()) {
setBrokenTokenWarning(true)
}
}
}, [outputCurrency])
useEffect(() => {
if (isNewExchange) {
setZeroDecimalError()
if (inputValue) {
const parsedInputValue = ethers.utils.parseUnits(inputValue, 18)
setInputValueParsed(parsedInputValue)
}
if (outputValue) {
try {
const parsedOutputValue = ethers.utils.parseUnits(outputValue, decimals)
setOutputValueParsed(parsedOutputValue)
} catch {
setZeroDecimalError('Invalid input. For 0 decimal tokens only supply whole number token amounts.')
}
}
}
}, [decimals, inputValue, isNewExchange, outputValue])
// parse input value
useEffect(() => {
if (
isNewExchange === false &&
inputValue &&
marketRate &&
lastEditedField === INPUT &&
(decimals || decimals === 0)
) {
try {
const parsedValue = ethers.utils.parseUnits(inputValue, 18)
if (parsedValue.lte(ethers.constants.Zero) || parsedValue.gte(ethers.constants.MaxUint256)) {
throw Error()
}
setInputValueParsed(parsedValue)
const currencyAmount = marketRate
.mul(parsedValue)
.div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18)))
.div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18 - decimals)))
setOutputValueParsed(currencyAmount)
dispatchAddLiquidityState({
type: 'UPDATE_DEPENDENT_VALUE',
payload: { field: OUTPUT, value: amountFormatter(currencyAmount, decimals, Math.min(decimals, 4), false) }
})
return () => {
setOutputError()
setInputValueParsed()
setOutputValueParsed()
dispatchAddLiquidityState({
type: 'UPDATE_DEPENDENT_VALUE',
payload: { field: OUTPUT, value: '' }
})
}
} catch {
setOutputError(t('inputNotValid'))
}
}
}, [inputValue, isNewExchange, lastEditedField, marketRate, decimals, t])
// parse output value
useEffect(() => {
if (
isNewExchange === false &&
outputValue &&
marketRateInverted &&
lastEditedField === OUTPUT &&
(decimals || decimals === 0)
) {
try {
const parsedValue = ethers.utils.parseUnits(outputValue, decimals)
if (parsedValue.lte(ethers.constants.Zero) || parsedValue.gte(ethers.constants.MaxUint256)) {
throw Error()
}
setOutputValueParsed(parsedValue)
const currencyAmount = marketRateInverted
.mul(parsedValue)
.div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(decimals)))
setInputValueParsed(currencyAmount)
dispatchAddLiquidityState({
type: 'UPDATE_DEPENDENT_VALUE',
payload: { field: INPUT, value: amountFormatter(currencyAmount, 18, 4, false) }
})
return () => {
setInputError()
setOutputValueParsed()
setInputValueParsed()
dispatchAddLiquidityState({
type: 'UPDATE_DEPENDENT_VALUE',
payload: { field: INPUT, value: '' }
})
}
} catch {
setInputError(t('inputNotValid'))
}
}
}, [outputValue, isNewExchange, lastEditedField, marketRateInverted, decimals, t])
// input validation
useEffect(() => {
if (inputValueParsed && inputBalance) {
if (inputValueParsed.gt(inputBalance)) {
setInputError(t('insufficientBalance'))
} else {
setInputError(null)
}
}
if (outputValueMax && outputBalance) {
if (outputValueMax.gt(outputBalance)) {
setOutputError(t('insufficientBalance'))
} else {
setOutputError(null)
}
}
}, [inputValueParsed, inputBalance, outputValueMax, outputBalance, t])
const allowance = useAddressAllowance(account, outputCurrency, exchangeAddress)
const [showUnlock, setShowUnlock] = useState(false)
useEffect(() => {
if (outputValueParsed && allowance) {
if (allowance.lt(outputValueParsed)) {
setOutputError(t('unlockTokenCont'))
setShowUnlock(true)
}
return () => {
setOutputError()
setShowUnlock(false)
}
}
}, [outputValueParsed, allowance, t])
const isActive = active && account
const isValid =
(inputError === null || outputError === null) && !zeroDecimalError && !showUnlock && !brokenTokenWarning
const newOutputDetected =
outputCurrency !== 'ETH' && outputCurrency && !INITIAL_TOKENS_CONTEXT[chainId].hasOwnProperty(outputCurrency)
const [showOutputWarning, setShowOutputWarning] = useState(false)
useEffect(() => {
if (newOutputDetected) {
setShowOutputWarning(true)
} else {
setShowOutputWarning(false)
}
}, [newOutputDetected, setShowOutputWarning])
return (
<>
{isNewExchange ? (
<NewExchangeWarning>
<NewExchangeWarningText>
<span role="img" aria-label="first-liquidity">
🚰
</span>{' '}
{t('firstLiquidity')}
</NewExchangeWarningText>
<NewExchangeWarningText>{t('initialExchangeRate', { symbol })}</NewExchangeWarningText>
</NewExchangeWarning>
) : null}
{showOutputWarning && (
<WarningCard
onDismiss={() => {
setShowOutputWarning(false)
}}
urlAddedTokens={urlAddedTokens}
currency={outputCurrency}
/>
)}
<CurrencyInputPanel
title={t('deposit')}
extraText={inputBalance && formatBalance(amountFormatter(inputBalance, 18, 4))}
onValueChange={inputValue => {
dispatchAddLiquidityState({ type: 'UPDATE_VALUE', payload: { value: inputValue, field: INPUT } })
}}
extraTextClickHander={() => {
if (inputBalance) {
const valueToSet = inputBalance.sub(ethers.utils.parseEther('.1'))
if (valueToSet.gt(ethers.constants.Zero)) {
dispatchAddLiquidityState({
type: 'UPDATE_VALUE',
payload: { value: amountFormatter(valueToSet, 18, 18, false), field: INPUT }
})
}
}
}}
selectedTokenAddress="ETH"
value={inputValue}
errorMessage={inputError}
disableTokenSelect
/>
<OversizedPanel>
<DownArrowBackground>
<ColoredWrappedPlus active={isActive} alt="plus" />
</DownArrowBackground>
</OversizedPanel>
<CurrencyInputPanel
title={t('deposit')}
description={isNewExchange ? '' : outputValue ? `(${t('estimated')})` : ''}
extraText={
outputBalance && decimals && formatBalance(amountFormatter(outputBalance, decimals, Math.min(decimals, 4)))
}
urlAddedTokens={urlAddedTokens}
selectedTokenAddress={outputCurrency}
onCurrencySelected={outputCurrency => {
dispatchAddLiquidityState({ type: 'SELECT_CURRENCY', payload: outputCurrency })
}}
onValueChange={outputValue => {
dispatchAddLiquidityState({ type: 'UPDATE_VALUE', payload: { value: outputValue, field: OUTPUT } })
}}
extraTextClickHander={() => {
if (outputBalance) {
dispatchAddLiquidityState({
type: 'UPDATE_VALUE',
payload: {
value: amountFormatter(calculateMaxOutputVal(outputBalance), decimals, decimals, false),
field: OUTPUT
}
})
}
}}
value={outputValue}
showUnlock={showUnlock}
errorMessage={outputError}
/>
<OversizedPanel hideBottom>
<SummaryPanel>
<ExchangeRateWrapper>
<ExchangeRate>{t('exchangeRate')}</ExchangeRate>
<span>{marketRate ? `1 ETH = ${amountFormatter(marketRate, 18, 4)} ${symbol}` : ' - '}</span>
</ExchangeRateWrapper>
<ExchangeRateWrapper>
<ExchangeRate>{t('currentPoolSize')}</ExchangeRate>
<span>
{exchangeETHBalance && exchangeTokenBalance
? `${amountFormatter(exchangeETHBalance, 18, 4)} ETH + ${amountFormatter(
exchangeTokenBalance,
decimals,
Math.min(4, decimals)
)} ${symbol}`
: ' - '}
</span>
</ExchangeRateWrapper>
<ExchangeRateWrapper>
<ExchangeRate>
{t('yourPoolShare')} ({exchangeETHBalance && amountFormatter(poolTokenPercentage, 16, 2)}%)
</ExchangeRate>
<span>
{ethShare && tokenShare
? `${amountFormatter(ethShare, 18, 4)} ETH + ${amountFormatter(
tokenShare,
decimals,
Math.min(4, decimals)
)} ${symbol}`
: ' - '}
</span>
</ExchangeRateWrapper>
</SummaryPanel>
</OversizedPanel>
{renderSummary()}
<Flex>
<Button disabled={!isValid} onClick={onAddLiquidity}>
{t('addLiquidity')}
</Button>
</Flex>
</>
)
}

@ -1,153 +0,0 @@
import React, { useState, useCallback } from 'react'
import { withRouter, NavLink } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import OversizedPanel from '../../components/OversizedPanel'
import { ReactComponent as Dropdown } from '../../assets/images/dropdown-blue.svg'
import Modal from '../../components/Modal'
import { useBodyKeyDown } from '../../hooks'
import { lighten } from 'polished'
const poolTabOrder = [
{
path: '/add-liquidity',
textKey: 'addLiquidity',
regex: /\/add-liquidity/
},
{
path: '/remove-liquidity',
textKey: 'removeLiquidity',
regex: /\/remove-liquidity/
},
{
path: '/create-exchange',
textKey: 'createExchange',
regex: /\/create-exchange.*/
}
]
const LiquidityContainer = styled.div`
${({ theme }) => theme.flexRowNoWrap};
align-items: center;
padding: 1rem 1rem;
font-size: 1rem;
color: ${({ theme }) => theme.royalBlue};
font-weight: 500;
cursor: pointer;
:hover {
color: ${({ theme }) => lighten(0.1, theme.royalBlue)};
}
img {
height: 0.75rem;
width: 0.75rem;
}
`
const LiquidityLabel = styled.span`
flex: 1 0 auto;
`
const activeClassName = 'MODE'
const StyledNavLink = styled(NavLink).attrs({
activeClassName
})`
${({ theme }) => theme.flexRowNoWrap}
padding: 1rem;
margin-left: 1rem;
margin-right: 1rem;
font-size: 1rem;
cursor: pointer;
text-decoration: none;
color: ${({ theme }) => theme.doveGray};
font-size: 1rem;
&.${activeClassName} {
background-color: ${({ theme }) => theme.inputBackground};
border-radius: 3rem;
border: 1px solid ${({ theme }) => theme.mercuryGray};
font-weight: 500;
color: ${({ theme }) => theme.royalBlue};
}
`
const PoolModal = styled.div`
background-color: ${({ theme }) => theme.inputBackground};
width: 100%;
height: 100%;
padding: 2rem 0 2rem 0;
`
const WrappedDropdown = ({ isError, highSlippageWarning, ...rest }) => <Dropdown {...rest} />
const ColoredDropdown = styled(WrappedDropdown)`
path {
stroke: ${({ theme }) => theme.royalBlue};
}
`
function ModeSelector({ location: { pathname }, history }) {
const { t } = useTranslation()
const [modalIsOpen, setModalIsOpen] = useState(false)
const activeTabKey = poolTabOrder[poolTabOrder.findIndex(({ regex }) => pathname.match(regex))].textKey
const navigate = useCallback(
direction => {
const tabIndex = poolTabOrder.findIndex(({ regex }) => pathname.match(regex))
history.push(poolTabOrder[(tabIndex + poolTabOrder.length + direction) % poolTabOrder.length].path)
},
[pathname, history]
)
const navigateRight = useCallback(() => {
navigate(1)
}, [navigate])
const navigateLeft = useCallback(() => {
navigate(-1)
}, [navigate])
useBodyKeyDown('ArrowDown', navigateRight, modalIsOpen)
useBodyKeyDown('ArrowUp', navigateLeft, modalIsOpen)
return (
<OversizedPanel hideTop>
<LiquidityContainer
onClick={() => {
setModalIsOpen(true)
}}
>
<LiquidityLabel>{t(activeTabKey)}</LiquidityLabel>
<ColoredDropdown alt="arrow down" />
</LiquidityContainer>
<Modal
isOpen={modalIsOpen}
maxHeight={50}
onDismiss={() => {
setModalIsOpen(false)
}}
>
<PoolModal>
{poolTabOrder.map(({ path, textKey, regex }) => (
<StyledNavLink
key={path}
to={path}
isActive={(_, { pathname }) => pathname.match(regex)}
onClick={() => {
setModalIsOpen(false)
}}
>
{t(textKey)}
</StyledNavLink>
))}
</PoolModal>
</Modal>
</OversizedPanel>
)
}
export default withRouter(ModeSelector)

@ -1,43 +0,0 @@
import React, { Suspense, lazy, useEffect } from 'react'
import ReactGA from 'react-ga'
import { Switch, Route, Redirect } from 'react-router-dom'
import ModeSelector from './ModeSelector'
const AddLiquidity = lazy(() => import('./AddLiquidity'))
const RemoveLiquidity = lazy(() => import('./RemoveLiquidity'))
const CreateExchange = lazy(() => import('./CreateExchange'))
export default function Pool({ params }) {
useEffect(() => {
ReactGA.pageview(window.location.pathname + window.location.search)
}, [])
const AddLiquidityParams = () => <AddLiquidity params={params} />
const RemoveLiquidityParams = () => <RemoveLiquidity params={params} />
const CreateExchangeParams = () => <CreateExchange params={params} />
return (
<>
<ModeSelector />
{/* this Suspense is for route code-splitting */}
<Suspense fallback={null}>
<Switch>
<Route exact strict path="/add-liquidity" component={AddLiquidityParams} />
<Route exact strict path="/remove-liquidity" component={RemoveLiquidityParams} />
<Route exact strict path="/create-exchange" component={CreateExchangeParams} />
<Route
path="/create-exchange/:tokenAddress"
render={({ match }) => {
return (
<Redirect to={{ pathname: '/create-exchange', state: { tokenAddress: match.params.tokenAddress } }} />
)
}}
/>
<Redirect to="/add-liquidity" />
</Switch>
</Suspense>
</>
)
}

@ -0,0 +1,446 @@
import React, { useReducer, useState, useCallback, useEffect } from 'react'
import { WETH, TokenAmount, JSBI, Percent, Route } from '@uniswap/sdk'
import { parseUnits, parseEther } from '@ethersproject/units'
import styled from 'styled-components'
import { Text } from 'rebass'
import { ChevronDown } from 'react-feather'
import { ButtonPrimary, ButtonEmpty } from '../../components/Button'
import ConfirmationModal from '../../components/ConfirmationModal'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import { RowBetween } from '../../components/Row'
import DoubleLogo from '../../components/DoubleLogo'
import { ArrowDown, Plus } from 'react-feather'
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import { LightCard } from '../../components/Card'
import SearchModal from '../../components/SearchModal'
import { useWeb3React } from '../../hooks'
import { useToken } from '../../contexts/Tokens'
import { useAddressBalance } from '../../contexts/Balances'
import { useExchange } from '../../contexts/Exchanges'
import { useExchangeContract } from '../../hooks'
const ErrorText = styled(Text)`
color: ${({ theme, error }) => (error ? theme.salmonRed : theme.chaliceGray)};
`
const ALLOWED_SLIPPAGE = JSBI.BigInt(200)
enum Field {
INPUT,
OUTPUT
}
interface AddState {
independentField: Field
typedValue: string
[Field.INPUT]: {
address: string | undefined
}
[Field.OUTPUT]: {
address: string | undefined
}
}
function initializeAddState(inputAddress?: string, outputAddress?: string): AddState {
return {
independentField: Field.INPUT,
typedValue: '',
[Field.INPUT]: {
address: inputAddress
},
[Field.OUTPUT]: {
address: outputAddress
}
}
}
enum AddAction {
SELECT_TOKEN,
SWITCH_TOKENS,
TYPE
}
interface Payload {
[AddAction.SELECT_TOKEN]: {
field: Field
address: string
}
[AddAction.SWITCH_TOKENS]: undefined
[AddAction.TYPE]: {
field: Field
typedValue: string
}
}
function reducer(
state: AddState,
action: {
type: AddAction
payload: Payload[AddAction]
}
): AddState {
switch (action.type) {
case AddAction.SELECT_TOKEN: {
const { field, address } = action.payload as Payload[AddAction.SELECT_TOKEN]
const otherField = field === Field.INPUT ? Field.OUTPUT : Field.INPUT
if (address === state[otherField].address) {
// the case where we have to swap the order
return {
...state,
independentField: state.independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT,
[field]: { address },
[otherField]: { address: state[field].address }
}
} else {
// the normal case
return {
...state,
[field]: { address }
}
}
}
case AddAction.TYPE: {
const { field, typedValue } = action.payload as Payload[AddAction.TYPE]
return {
...state,
independentField: field,
typedValue
}
}
default: {
throw Error
}
}
}
export default function AddLiquidity() {
// mock to set initial values either from URL or route from supply page
const token1 = '0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735'
const token0 = '0xc778417E063141139Fce010982780140Aa0cD5Ab'
const { account, chainId, library } = useWeb3React()
// modal state
const [showSearch, toggleSearch] = useState(false)
// input state
const [state, dispatch] = useReducer(reducer, initializeAddState(token0, token1))
const { independentField, typedValue, ...fieldData } = state
// get derived state
const dependentField = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
// get basic SDK entities
const tokens = {
[Field.INPUT]: useToken(fieldData[Field.INPUT].address),
[Field.OUTPUT]: useToken(fieldData[Field.OUTPUT].address)
}
const exchange = useExchange(tokens[Field.INPUT], tokens[Field.OUTPUT])
const route = exchange ? new Route([exchange], tokens[independentField]) : undefined
// get user- and token-specific lookup data
const userBalances = {
[Field.INPUT]: useAddressBalance(account, tokens[Field.INPUT]),
[Field.OUTPUT]: useAddressBalance(account, tokens[Field.OUTPUT])
}
const parsedAmounts: { [field: number]: TokenAmount } = {}
// try to parse typed value
if (typedValue !== '' && typedValue !== '.' && tokens[independentField]) {
try {
const typedValueParsed = parseUnits(typedValue, tokens[independentField].decimals).toString()
if (typedValueParsed !== '0')
parsedAmounts[independentField] = new TokenAmount(tokens[independentField], typedValueParsed)
} catch (error) {
// should only fail if the user specifies too many decimal places of precision (or maybe exceed max uint?)
console.error(error)
}
}
// get the price data and update dependent field
if (
route &&
parsedAmounts[independentField] &&
JSBI.greaterThan(parsedAmounts[independentField].raw, JSBI.BigInt(0))
) {
parsedAmounts[dependentField] = route.midPrice.quote(parsedAmounts[independentField])
}
// get formatted amounts
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: parsedAmounts[dependentField] ? parsedAmounts[dependentField].toSignificant(8) : ''
}
// pool token data
const poolToken = useToken(exchange?.address)
const [totalPoolTokens, setTotalPoolTokens] = useState<TokenAmount>()
const exchangeContract = useExchangeContract(exchange?.address)
const fetchPoolTokens = useCallback(() => {
if (exchangeContract) {
exchangeContract.totalSupply().then(totalSupply => {
if (totalSupply !== undefined && poolToken?.decimals) {
const supplyFormatted = JSBI.BigInt(totalSupply)
const tokenSupplyFormatted = new TokenAmount(poolToken, supplyFormatted)
setTotalPoolTokens(tokenSupplyFormatted)
}
})
}
}, [exchangeContract, poolToken])
useEffect(() => {
fetchPoolTokens()
library.on('block', fetchPoolTokens)
return () => {
library.removeListener('block', fetchPoolTokens)
}
}, [fetchPoolTokens, library])
function minTokenAmount(x: JSBI, y: JSBI): JSBI {
return JSBI.lessThan(x, y) ? x : y
}
// check for estimated liquidity minted
const liquidityMinted =
!!poolToken && !!parsedAmounts[Field.INPUT] && !!parsedAmounts[Field.OUTPUT] && !!totalPoolTokens && exchange
? new TokenAmount(
poolToken,
minTokenAmount(
JSBI.divide(
JSBI.multiply(parsedAmounts[Field.INPUT].raw, totalPoolTokens.raw),
exchange.reserveOf(tokens[Field.INPUT]).raw
),
JSBI.divide(
JSBI.multiply(parsedAmounts[Field.OUTPUT].raw, totalPoolTokens.raw),
exchange.reserveOf(tokens[Field.OUTPUT]).raw
)
)
)
: undefined
const poolTokenPercentage =
!!liquidityMinted && !!totalPoolTokens
? new Percent(liquidityMinted.raw, totalPoolTokens.add(liquidityMinted).raw)
: undefined
const onTokenSelection = useCallback((field: Field, address: string) => {
dispatch({
type: AddAction.SELECT_TOKEN,
payload: { field, address }
})
}, [])
const onUserInput = useCallback((field: Field, typedValue: string) => {
dispatch({ type: AddAction.TYPE, payload: { field, typedValue } })
}, [])
const onMaxInput = useCallback((typedValue: string) => {
dispatch({
type: AddAction.TYPE,
payload: {
field: Field.INPUT,
typedValue
}
})
}, [])
const onMaxOutput = useCallback((typedValue: string) => {
dispatch({
type: AddAction.TYPE,
payload: {
field: Field.OUTPUT,
typedValue
}
})
}, [])
const MIN_ETHER = new TokenAmount(WETH[chainId], JSBI.BigInt(parseEther('.01')))
const maxAmountInput =
!!userBalances[Field.INPUT] &&
JSBI.greaterThan(
userBalances[Field.INPUT].raw,
tokens[Field.INPUT].equals(WETH[chainId]) ? MIN_ETHER.raw : JSBI.BigInt(0)
)
? tokens[Field.INPUT].equals(WETH[chainId])
? userBalances[Field.INPUT].subtract(MIN_ETHER)
: userBalances[Field.INPUT]
: undefined
const atMaxAmountInput =
!!maxAmountInput && !!parsedAmounts[Field.INPUT]
? JSBI.equal(maxAmountInput.raw, parsedAmounts[Field.INPUT].raw)
: undefined
const maxAmountOutput =
!!userBalances[Field.OUTPUT] &&
JSBI.greaterThan(
userBalances[Field.OUTPUT].raw,
tokens[Field.OUTPUT].equals(WETH[chainId]) ? MIN_ETHER.raw : JSBI.BigInt(0)
)
? tokens[Field.OUTPUT].equals(WETH[chainId])
? userBalances[Field.OUTPUT].subtract(MIN_ETHER)
: userBalances[Field.OUTPUT]
: undefined
const atMaxAmountOutput =
!!maxAmountOutput && !!parsedAmounts[Field.OUTPUT]
? JSBI.equal(maxAmountOutput.raw, parsedAmounts[Field.OUTPUT].raw)
: undefined
// state for confirmation popup
const [showConfirm, toggleConfirm] = useState(false)
// errors
const [inputError, setInputError] = useState()
const [outputError, setOutputError] = useState()
const [errorText, setErrorText] = useState(' ')
const [isError, setIsError] = useState(false)
// update errors live
useEffect(() => {
// reset errors
setInputError(null)
setOutputError(null)
setIsError(false)
if (parseFloat(parsedAmounts?.[Field.INPUT]?.toExact()) > parseFloat(userBalances?.[Field.INPUT]?.toExact())) {
setInputError('Insufficient balance.')
setIsError(true)
}
if (parseFloat(parsedAmounts?.[Field.OUTPUT]?.toExact()) > parseFloat(userBalances?.[Field.OUTPUT]?.toExact())) {
setOutputError('Insufficient balance.')
setIsError(true)
}
}, [parsedAmounts, userBalances])
// set error text based on all errors
useEffect(() => {
setErrorText(null)
if (!parsedAmounts[Field.INPUT]) {
setErrorText('Enter an amount to continue')
} else if (outputError) {
setErrorText(outputError)
} else if (inputError) {
setErrorText(inputError)
return
}
}, [inputError, outputError, parsedAmounts])
// error state for button
const isValid = !errorText
return (
<>
<ConfirmationModal
isOpen={showConfirm}
onDismiss={() => {
toggleConfirm(false)
}}
liquidityMinted={liquidityMinted}
amount0={
parsedAmounts[independentField]?.token.equals(exchange?.token0)
? parsedAmounts[independentField]
: parsedAmounts[dependentField]
}
amount1={
parsedAmounts[independentField]?.token.equals(exchange?.token0)
? parsedAmounts[dependentField]
: parsedAmounts[independentField]
}
poolTokenPercentage={poolTokenPercentage}
price={route?.midPrice}
/>
<SearchModal
isOpen={showSearch}
onDismiss={() => {
toggleSearch(false)
}}
/>
<AutoColumn gap="20px">
<ButtonEmpty
padding={'1rem'}
onClick={() => {
toggleSearch(true)
}}
>
<RowBetween>
<DoubleLogo a0={exchange?.token0?.address || ''} a1={exchange?.token1?.address || ''} size={24} />
<Text fontSize={20}>
{exchange?.token0?.symbol && exchange?.token1?.symbol
? exchange?.token0?.symbol + ' / ' + exchange?.token1?.symbol + ' Pool'
: ''}
</Text>
<ChevronDown size={24} />
</RowBetween>
</ButtonEmpty>
<CurrencyInputPanel
field={Field.INPUT}
value={formattedAmounts[Field.INPUT]}
onUserInput={onUserInput}
onMax={() => {
onMaxInput(maxAmountInput.toExact())
}}
atMax={atMaxAmountInput}
selectedTokenAddress={tokens[Field.INPUT]?.address}
onTokenSelection={onTokenSelection}
title={'Deposit'}
error={inputError}
/>
<ColumnCenter>
<Plus size="16" color="#888D9B" />
</ColumnCenter>
<CurrencyInputPanel
field={Field.OUTPUT}
value={formattedAmounts[Field.OUTPUT]}
onUserInput={onUserInput}
onMax={() => {
onMaxOutput(maxAmountOutput.toExact())
}}
atMax={atMaxAmountOutput}
selectedTokenAddress={tokens[Field.OUTPUT]?.address}
onTokenSelection={onTokenSelection}
title={'Deposit'}
error={outputError}
/>
<ColumnCenter>
<ArrowDown size="16" color="#888D9B" />
</ColumnCenter>
<LightCard>
<AutoColumn gap="10px">
<RowBetween>
Minted pool tokens:
<div>{liquidityMinted ? liquidityMinted.toFixed(6) : '-'}</div>
</RowBetween>
<RowBetween>
Minted pool share:
<div>{poolTokenPercentage ? +poolTokenPercentage.toFixed(4) + '%' : '-'}</div>
</RowBetween>
<RowBetween>
Rate:
<div>
1 {exchange?.token0.symbol} = {route?.midPrice.toSignificant(6)} {exchange?.token1.symbol}
</div>
</RowBetween>
</AutoColumn>
</LightCard>
<ColumnCenter style={{ height: '20px' }}>
<ErrorText fontSize={12} error={isError}>
{errorText && errorText}
</ErrorText>
</ColumnCenter>
<ButtonPrimary
onClick={() => {
toggleConfirm(true)
}}
disabled={!isValid}
>
<Text fontSize={20} fontWeight={500}>
Supply
</Text>
</ButtonPrimary>
</AutoColumn>
</>
)
}

@ -7,7 +7,7 @@ import styled from 'styled-components'
import { useWeb3React, useExchangeContract } from '../../hooks'
import { useTransactionAdder } from '../../contexts/Transactions'
import { useTokenDetails, INITIAL_TOKENS_CONTEXT } from '../../contexts/Tokens'
import { useToken, INITIAL_TOKENS_CONTEXT } from '../../contexts/Tokens'
import { useAddressBalance } from '../../contexts/Balances'
import { calculateGasMargin, amountFormatter } from '../../utils'
@ -188,7 +188,7 @@ export default function RemoveLiquidity({ params }) {
}
}, [t, value])
const { symbol, decimals, exchangeAddress } = useTokenDetails(outputCurrency)
const { symbol, decimals, exchangeAddress } = useToken(outputCurrency)
const [totalPoolTokens, setTotalPoolTokens] = useState()
const poolTokenBalance = useAddressBalance(account, exchangeAddress)

236
src/pages/Supply/index.js Normal file

@ -0,0 +1,236 @@
import React, { useEffect, useState, useCallback } from 'react'
import styled from 'styled-components'
import { TokenAmount, JSBI, Percent, Token } from '@uniswap/sdk'
import ReactGA from 'react-ga'
import { withRouter } from 'react-router-dom'
import { Link } from '../../theme'
import { AutoColumn } from '../../components/Column'
import Row, { RowBetween, RowFixed } from '../../components/Row'
import { ButtonDropwdown, ButtonSecondary } from '../../components/Button'
import Card from '../../components/Card'
import { Text } from 'rebass'
import DoubleLogo from '../../components/DoubleLogo'
import SearchModal from '../../components/SearchModal'
import { ArrowRight } from 'react-feather'
import { useAllExchanges } from '../../contexts/Exchanges'
import { useAllBalances } from '../../contexts/Balances'
import { useWeb3React } from '@web3-react/core'
import { useAllTokens } from '../../contexts/Tokens'
import { useExchangeContract } from '../../hooks'
import TokenLogo from '../../components/TokenLogo'
const Positions = styled.div`
position: relative;
margin-top: 38px;
`
const FixedBottom = styled.div`
position: absolute;
bottom: -240px;
width: 100%;
`
function ExchangeCard({ exchangeAddress, token0, token1, history, allBalances }) {
const { account, chainId } = useWeb3React()
const userPoolBalance = allBalances?.[account]?.[exchangeAddress]
const [totalPoolTokens, setTotalPoolTokens] = useState()
// get the total pool token supply
const exchangeContract = useExchangeContract(exchangeAddress)
const fetchPoolTokens = useCallback(() => {
if (exchangeContract) {
exchangeContract.totalSupply().then(totalSupply => {
if (totalSupply !== undefined) {
const supplyFormatted = JSBI.BigInt(totalSupply)
const tokenSupplyFormatted = new TokenAmount(new Token(chainId, exchangeAddress, 18), supplyFormatted)
setTotalPoolTokens(tokenSupplyFormatted)
}
})
}
}, [exchangeContract, chainId, exchangeAddress])
useEffect(() => {
fetchPoolTokens()
}, [fetchPoolTokens])
const poolTokenPercentage =
!!userPoolBalance && !!totalPoolTokens ? new Percent(userPoolBalance.raw, totalPoolTokens.raw) : undefined
const token0Deposited = poolTokenPercentage?.multiply(allBalances[exchangeAddress][token0.address])
const token1Deposited = poolTokenPercentage?.multiply(allBalances[exchangeAddress][token1.address])
return (
<Card border="1px solid #EDEEF2">
<AutoColumn gap="20px">
<RowBetween>
<RowFixed>
<DoubleLogo a0={token0?.address || ''} a1={token1?.address || ''} margin={true} />
<Text fontWeight={500} fontSize={20}>
{token0?.symbol}:{token1?.symbol}
</Text>
</RowFixed>
<Text fontWeight={500} fontSize={20}>
{userPoolBalance ? userPoolBalance.toFixed(6) : '-'}
</Text>
</RowBetween>
<AutoColumn gap="12px">
<RowBetween>
<Text color="#888D9B" fontSize={16} fontWeight={500}>
{token0?.symbol} Deposited:
</Text>
{token0Deposited ? (
<RowFixed>
<TokenLogo address={token0?.address || ''} />
<Text color="#888D9B" fontSize={16} fontWeight={500}>
{token0Deposited?.toSignificant(6)}
</Text>
</RowFixed>
) : (
'-'
)}
</RowBetween>
<RowBetween>
<Text color="#888D9B" fontSize={16} fontWeight={500}>
{token1?.symbol} Deposited:
</Text>
{token1Deposited ? (
<RowFixed>
<TokenLogo address={token1.address || ''} />
<Text color="#888D9B" fontSize={16} fontWeight={500}>
{token1Deposited?.toSignificant(6)}
</Text>
</RowFixed>
) : (
'-'
)}
</RowBetween>
<RowBetween>
<Text color="#888D9B" fontSize={16} fontWeight={500}>
Your pool share:
</Text>
<Text color="#888D9B" fontSize={16} fontWeight={500}>
{poolTokenPercentage ? poolTokenPercentage.toFixed(2) + '%' : '-'}
</Text>
</RowBetween>
</AutoColumn>
<RowBetween>
<ButtonSecondary
width="48%"
onClick={() => {
history.push('/add/' + token0?.address + '-' + token1?.address)
}}
>
Add
</ButtonSecondary>
<ButtonSecondary
width="48%"
onClick={() => {
history.push('/remove')
}}
>
Remove
</ButtonSecondary>
</RowBetween>
</AutoColumn>
</Card>
)
}
function Supply({ history }) {
useEffect(() => {
ReactGA.pageview(window.location.pathname + window.location.search)
}, [])
const { account } = useWeb3React()
const [showSearch, toggleSearch] = useState(false)
const exchanges = useAllExchanges()
const allTokens = useAllTokens()
const allBalances = useAllBalances()
const filteredPairList = Object.keys(exchanges).map((token0Address, i) => {
return Object.keys(exchanges[token0Address]).map(token1Address => {
const exchangeAddress = exchanges[token0Address][token1Address]
/**
* we need the users exchnage balance over all exchanges
*
* right now we dont
*
* if they go to supplu page, flip switch to look for balances
*
*
*
*/
// gate on positive address
if (allBalances?.[account]?.[exchangeAddress]) {
const token0 = allTokens[token0Address]
const token1 = allTokens[token1Address]
return (
<ExchangeCard
history={history}
key={i}
exchangeAddress={exchangeAddress}
token0={token0}
token1={token1}
allBalances={allBalances}
/>
)
}
return ''
})
})
return (
<>
<ButtonDropwdown
onClick={() => {
toggleSearch(true)
}}
>
<Text fontSize={20}>Find a pool</Text>
</ButtonDropwdown>
<Positions>
<AutoColumn gap="20px">
<RowBetween>
<Text fontWeight={500}>Your positions</Text>
<Link>
<Text fontWeight={500}>View on Uniswap.info</Text>
</Link>
</RowBetween>
{filteredPairList}
</AutoColumn>
<FixedBottom>
<Card bg="rgba(255, 255, 255, 0.6)" padding={'24px'}>
<AutoColumn gap="30px">
<Text fontSize="20px" fontWeight={500}>
Earn fees with pooled market making.
</Text>
<Text fontSize="12px">
<Link>Provide liquidity </Link>to earn .03% spread fees for providing market depth.
</Text>
<Link>
<Row>
Learn More <ArrowRight size="16" />
</Row>
</Link>
</AutoColumn>
</Card>
</FixedBottom>
</Positions>
<SearchModal
isOpen={showSearch}
onDismiss={() => {
toggleSearch(false)
}}
/>
</>
)
}
export default withRouter(Supply)

1
src/react-app-env.d.ts vendored Normal file

@ -0,0 +1 @@
/// <reference types="react-scripts" />

@ -1,5 +1,6 @@
import styled, { keyframes } from 'styled-components'
import { darken } from 'polished'
import { X } from 'react-feather'
export const Button = styled.button.attrs(({ warning, theme }) => ({
backgroundColor: warning ? theme.salmonRed : theme.royalBlue
@ -31,6 +32,12 @@ export const Button = styled.button.attrs(({ warning, theme }) => ({
}
`
export const CloseIcon = styled(X)`
:hover {
cursor: pointer;
}
`
export const Link = styled.a.attrs({
target: '_blank',
rel: 'noopener noreferrer'

@ -45,15 +45,20 @@ const theme = darkMode => ({
black,
textColor: darkMode ? white : '#010101',
greyText: darkMode ? white : '#6C7284',
fadedText: darkMode ? white : '#C3C5CB',
panelBackground: darkMode ? '#292C2F' : '#FFFFFF',
// for setting css on <html>
backgroundColor: darkMode ? '#333639' : white,
backgroundColor: darkMode ? '#333639' : '#F7F8FA',
modalBackground: darkMode ? 'rgba(0,0,0,0.6)' : 'rgba(0,0,0,0.5)',
inputBackground: darkMode ? '#202124' : white,
placeholderGray: darkMode ? '#5F5F5F' : '#E1E1E1',
shadowColor: darkMode ? '#000' : '#2F80ED',
buttonBackgroundPlain: darkMode ? '#333639' : white,
buttonOutlinePlain: darkMode ? '#292C2F' : white,
// grays
concreteGray: darkMode ? '#292C2F' : '#FAFAFA',
mercuryGray: darkMode ? '#333333' : '#E1E1E1',
@ -65,12 +70,15 @@ const theme = darkMode => ({
buttonOutlineGrey: darkMode ? '#FAFAFA' : '#F2F2F2',
tokenRowHover: darkMode ? '#404040' : '#F2F2F2',
outlineGrey: darkMode ? '#292C2F' : '#EDEEF2',
//blacks
charcoalBlack: darkMode ? '#F2F2F2' : '#404040',
// blues
zumthorBlue: darkMode ? '#212529' : '#EBF4FF',
malibuBlue: darkMode ? '#E67AEF' : '#5CA2FF',
royalBlue: darkMode ? '#DC6BE5' : '#2F80ED',
disabledBlue: darkMode ? '#2172E5' : '#2172E5',
loadingBlue: darkMode ? '#e4f0ff' : '#e4f0ff',
// purples

@ -5,7 +5,6 @@ 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, SUPPORTED_THEMES } from '../constants'
import { formatFixed } from '@uniswap/sdk'
import UncheckedJsonRpcSigner from './signer'
@ -256,11 +255,7 @@ export function formatTokenBalance(balance, decimal) {
export function formatToUsd(price) {
const format = { decimalSeparator: '.', groupSeparator: ',', groupSize: 3 }
const usdPrice = formatFixed(price, {
decimalPlaces: 2,
dropTrailingZeros: false,
format
})
const usdPrice = 1
return usdPrice
}

@ -21,15 +21,5 @@ export function getUSDPrice(reserves) {
ethPrices,
forEachStablecoin(i => reserves[i].ethReserve.amount)
)
// 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 getMean([median, mean, weightedMean])[0]
}

31
tsconfig.json Normal file

@ -0,0 +1,31 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"downlevelIteration": true,
"allowSyntheticDefaultImports": true
},
"exclude": [
"node_modules"
],
"include": [
"**/*.js",
"**/*.ts",
"**/*.tsx"
]
}

9
webpack.config Normal file

@ -0,0 +1,9 @@
module.exports = {
entry: [
path.join(process.cwd(), 'app/app.tsx'), // or whatever the path of your root file is
]
module: {
rules:[{ test: /\.tsx?$/, loader: 'awesome-typescript-loader' }], // other loader configuration goes in the array
resolve: {extensions: ['.js', '.jsx', '.react.js', '.ts', '.tsx']}
}
}

642
yarn.lock

@ -1090,23 +1090,159 @@
resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18"
integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==
"@emotion/is-prop-valid@^0.8.1":
"@emotion/cache@^10.0.27":
version "10.0.27"
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-10.0.27.tgz#7895db204e2c1a991ae33d51262a3a44f6737303"
integrity sha512-Zp8BEpbMunFsTcqAK4D7YTm3MvCp1SekflSLJH8lze2fCcSZ/yMkXHo8kb3t1/1Tdd3hAqf3Fb7z9VZ+FMiC9w==
dependencies:
"@emotion/sheet" "0.9.4"
"@emotion/stylis" "0.8.5"
"@emotion/utils" "0.11.3"
"@emotion/weak-memoize" "0.2.5"
"@emotion/core@^10.0.0":
version "10.0.27"
resolved "https://registry.yarnpkg.com/@emotion/core/-/core-10.0.27.tgz#7c3f78be681ab2273f3bf11ca3e2edc4a9dd1fdc"
integrity sha512-XbD5R36pVbohQMnKfajHv43g8EbN4NHdF6Zh9zg/C0nr0jqwOw3gYnC07Xj3yG43OYSRyrGsoQ5qPwc8ycvLZw==
dependencies:
"@babel/runtime" "^7.5.5"
"@emotion/cache" "^10.0.27"
"@emotion/css" "^10.0.27"
"@emotion/serialize" "^0.11.15"
"@emotion/sheet" "0.9.4"
"@emotion/utils" "0.11.3"
"@emotion/css@^10.0.27":
version "10.0.27"
resolved "https://registry.yarnpkg.com/@emotion/css/-/css-10.0.27.tgz#3a7458198fbbebb53b01b2b87f64e5e21241e14c"
integrity sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw==
dependencies:
"@emotion/serialize" "^0.11.15"
"@emotion/utils" "0.11.3"
babel-plugin-emotion "^10.0.27"
"@emotion/hash@0.7.4":
version "0.7.4"
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.7.4.tgz#f14932887422c9056b15a8d222a9074a7dfa2831"
integrity sha512-fxfMSBMX3tlIbKUdtGKxqB1fyrH6gVrX39Gsv3y8lRYKUqlgDt3UMqQyGnR1bQMa2B8aGnhLZokZgg8vT0Le+A==
"@emotion/is-prop-valid@0.8.6", "@emotion/is-prop-valid@^0.8.1":
version "0.8.6"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.6.tgz#4757646f0a58e9dec614c47c838e7147d88c263c"
integrity sha512-mnZMho3Sq8BfzkYYRVc8ilQTnc8U02Ytp6J1AwM6taQStZ3AhsEJBX2LzhA/LJirNCwM2VtHL3VFIZ+sNJUgUQ==
dependencies:
"@emotion/memoize" "0.7.4"
"@emotion/memoize@0.7.4":
"@emotion/memoize@0.7.4", "@emotion/memoize@^0.7.1":
version "0.7.4"
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb"
integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==
"@emotion/unitless@^0.7.0":
"@emotion/serialize@^0.11.15":
version "0.11.15"
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-0.11.15.tgz#9a0f5873fb458d87d4f23e034413c12ed60a705a"
integrity sha512-YE+qnrmGwyR+XB5j7Bi+0GT1JWsdcjM/d4POu+TXkcnrRs4RFCCsi3d/Ebf+wSStHqAlTT2+dfd+b9N9EO2KBg==
dependencies:
"@emotion/hash" "0.7.4"
"@emotion/memoize" "0.7.4"
"@emotion/unitless" "0.7.5"
"@emotion/utils" "0.11.3"
csstype "^2.5.7"
"@emotion/sheet@0.9.4":
version "0.9.4"
resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-0.9.4.tgz#894374bea39ec30f489bbfc3438192b9774d32e5"
integrity sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA==
"@emotion/styled-base@^10.0.27":
version "10.0.27"
resolved "https://registry.yarnpkg.com/@emotion/styled-base/-/styled-base-10.0.27.tgz#d9efa307ae4e938fcc4d0596b40b7e8bc10f7c7c"
integrity sha512-ufHM/HhE3nr309hJG9jxuFt71r6aHn7p+bwXduFxcwPFEfBIqvmZUMtZ9YxIsY61PVwK3bp4G1XhaCzy9smVvw==
dependencies:
"@babel/runtime" "^7.5.5"
"@emotion/is-prop-valid" "0.8.6"
"@emotion/serialize" "^0.11.15"
"@emotion/utils" "0.11.3"
"@emotion/styled@^10.0.0":
version "10.0.27"
resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-10.0.27.tgz#12cb67e91f7ad7431e1875b1d83a94b814133eaf"
integrity sha512-iK/8Sh7+NLJzyp9a5+vIQIXTYxfT4yB/OJbjzQanB2RZpvmzBQOHZWhpAMZWYEKRNNbsD6WfBw5sVWkb6WzS/Q==
dependencies:
"@emotion/styled-base" "^10.0.27"
babel-plugin-emotion "^10.0.27"
"@emotion/stylis@0.8.5":
version "0.8.5"
resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04"
integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==
"@emotion/unitless@0.7.5", "@emotion/unitless@^0.7.0":
version "0.7.5"
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed"
integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==
"@emotion/utils@0.11.3":
version "0.11.3"
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-0.11.3.tgz#a759863867befa7e583400d322652a3f44820924"
integrity sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==
"@emotion/weak-memoize@0.2.5":
version "0.2.5"
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46"
integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==
"@ethersproject/abi@>=5.0.0-beta.137":
version "5.0.0-beta.145"
resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.0.0-beta.145.tgz#dd80c3e7fd01f9c13d3bb4cff543e56afbbf7600"
integrity sha512-buO1aiUe408qo8guMzwvzezUVKmQyhM21sbNaG8XrktrnxTNLvRghN1NYbtQi8Ov0ZT+P93VPz1whKu1Y2s2Xg==
dependencies:
"@ethersproject/address" ">=5.0.0-beta.128"
"@ethersproject/bignumber" ">=5.0.0-beta.130"
"@ethersproject/bytes" ">=5.0.0-beta.129"
"@ethersproject/constants" ">=5.0.0-beta.128"
"@ethersproject/hash" ">=5.0.0-beta.128"
"@ethersproject/keccak256" ">=5.0.0-beta.127"
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/properties" ">=5.0.0-beta.131"
"@ethersproject/strings" ">=5.0.0-beta.130"
"@ethersproject/abstract-provider@>=5.0.0-beta.131":
version "5.0.0-beta.138"
resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.0.0-beta.138.tgz#0e960ef55dc7af59f0a873cedd2f5e6e50941352"
integrity sha512-bIEBUMm62TxTF+zHOUHPfMQLf3gvN5cVR1Gyv5/LVuDz3UESgFTBHBpFLHPlW2/RLHDo7K0jx16a38vs6/r2gQ==
dependencies:
"@ethersproject/bignumber" ">=5.0.0-beta.130"
"@ethersproject/bytes" ">=5.0.0-beta.129"
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/networks" ">=5.0.0-beta.129"
"@ethersproject/properties" ">=5.0.0-beta.131"
"@ethersproject/transactions" ">=5.0.0-beta.128"
"@ethersproject/web" ">=5.0.0-beta.129"
"@ethersproject/abstract-signer@>=5.0.0-beta.132":
version "5.0.0-beta.140"
resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.0.0-beta.140.tgz#46b97f812740a1a7603960da2a85029f7e504f12"
integrity sha512-fROSxYPhtXqxK1y6/mJMpCUcjfUzCSuqNfACcPy2pwoOHMz1hsqS7m7HOEXy0HGAsQPw4fVuo4fywfA+Q62kmQ==
dependencies:
"@ethersproject/abstract-provider" ">=5.0.0-beta.131"
"@ethersproject/bignumber" ">=5.0.0-beta.130"
"@ethersproject/bytes" ">=5.0.0-beta.129"
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/properties" ">=5.0.0-beta.131"
"@ethersproject/address@>=5.0.0-beta.128", "@ethersproject/address@^5.0.0-beta.134":
version "5.0.0-beta.134"
resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.0.0-beta.134.tgz#9c1790c87b763dc547ac12e2dbc9fa78d0799a71"
integrity sha512-FHhUVJTUIg2pXvOOhIt8sB1cQbcwrzZKzf9CPV7JM1auli20nGoYhyMFYGK7u++GXzTMJduIkU1OwlIBupewDw==
dependencies:
"@ethersproject/bignumber" ">=5.0.0-beta.130"
"@ethersproject/bytes" ">=5.0.0-beta.129"
"@ethersproject/keccak256" ">=5.0.0-beta.127"
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/rlp" ">=5.0.0-beta.126"
bn.js "^4.4.0"
"@ethersproject/address@^5.0.0-beta.125":
version "5.0.0-beta.133"
resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.0.0-beta.133.tgz#b6bf2c298da5701c61038e4cdfac2e0038b884ce"
@ -1119,6 +1255,13 @@
"@ethersproject/rlp" ">=5.0.0-beta.126"
bn.js "^4.4.0"
"@ethersproject/base64@>=5.0.0-beta.126":
version "5.0.0-beta.131"
resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.0.0-beta.131.tgz#eb308ed6fd614cb1a7bc0bb50fd05abedb6ca965"
integrity sha512-pjgZZyDlGpSBkbuO87hnmVrOa92znIt5EIGBW1Mly5Nby8PU4YVwK3WoRP2vGd7hViNVLPCgfbmhh6LQhWK1sg==
dependencies:
"@ethersproject/bytes" ">=5.0.0-beta.129"
"@ethersproject/bignumber@>=5.0.0-beta.130":
version "5.0.0-beta.135"
resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.0.0-beta.135.tgz#9d464df8967f5d314d109497e4f25ab82314c098"
@ -1143,7 +1286,33 @@
dependencies:
"@ethersproject/bignumber" ">=5.0.0-beta.130"
"@ethersproject/keccak256@>=5.0.0-beta.127", "@ethersproject/keccak256@^5.0.0-beta.130":
"@ethersproject/contracts@^5.0.0-beta.143":
version "5.0.0-beta.143"
resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.0.0-beta.143.tgz#5fc2ed962f3364534b80048cb742a231354a4b80"
integrity sha512-xjWNlnbhfAkWxBW1ICi6l/O9iplf6GW8IlN57DddERCuzJnj3t2g3PinVTacH27ySqApnWF2FbFQIf0BRYp5hQ==
dependencies:
"@ethersproject/abi" ">=5.0.0-beta.137"
"@ethersproject/abstract-provider" ">=5.0.0-beta.131"
"@ethersproject/abstract-signer" ">=5.0.0-beta.132"
"@ethersproject/address" ">=5.0.0-beta.128"
"@ethersproject/bignumber" ">=5.0.0-beta.130"
"@ethersproject/bytes" ">=5.0.0-beta.129"
"@ethersproject/constants" ">=5.0.0-beta.128"
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/properties" ">=5.0.0-beta.131"
"@ethersproject/transactions" ">=5.0.0-beta.128"
"@ethersproject/hash@>=5.0.0-beta.128":
version "5.0.0-beta.133"
resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.0.0-beta.133.tgz#bda0c74454a82359642033f27c5157963495fcdf"
integrity sha512-tfF11QxFlJCy92rMtUZ0kImchWhlYXkN5Gj5cYfTcCdWEUKwNq1LljDnlrjV2JabO6s5enb8uiUj4RBTo2+Rgw==
dependencies:
"@ethersproject/bytes" ">=5.0.0-beta.129"
"@ethersproject/keccak256" ">=5.0.0-beta.127"
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/strings" ">=5.0.0-beta.130"
"@ethersproject/keccak256@>=5.0.0-beta.127", "@ethersproject/keccak256@^5.0.0-beta.130", "@ethersproject/keccak256@^5.0.0-beta.131":
version "5.0.0-beta.131"
resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.0.0-beta.131.tgz#b5778723ee75208065b9b9ad30c71d480f41bb31"
integrity sha512-KQnqMwGV0IMOjAr/UTFO8DuLrmN1uaMvcV3zh9hiXhh3rCuY+WXdeUh49w1VQ94kBKmaP0qfGb7z4SdhUWUHjw==
@ -1156,6 +1325,13 @@
resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.0.0-beta.133.tgz#2d62d495ed413c7045054d4f99a0fb4920079b2e"
integrity sha512-1ISf7rFKFbMHlEB37JS7Oy3FgFlvzF2Ze2uFZMJHGKp9xgDvFy1VHNMBM1KrJPK4AqCZXww0//e2keLsN3g/Cw==
"@ethersproject/networks@>=5.0.0-beta.129", "@ethersproject/networks@^5.0.0-beta.135":
version "5.0.0-beta.135"
resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.0.0-beta.135.tgz#c4a19ea19e01ec84d8b6a4ae75994b7417b694fa"
integrity sha512-9wesbAlsewNe0dU8B/lhK449GZml+08Opjf6nFpcV8BwWlYnPLs7EnOsK3GpMM0KDPZlksVYltyq+X1B7YxOcA==
dependencies:
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/properties@>=5.0.0-beta.131":
version "5.0.0-beta.135"
resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.0.0-beta.135.tgz#51a0a5d72ca034b5ae845d43ed409eb3576a3ca7"
@ -1163,6 +1339,35 @@
dependencies:
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/providers@^5.0.0-beta.153":
version "5.0.0-beta.153"
resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.0.0-beta.153.tgz#a24923146870af50cdcfefbd4eeef77bfdda5507"
integrity sha512-FJ/dNM5fj25m1XAEJzdC1B6GM9On3eRn/Vls6cUcSBWTOfKsTRf0uRSkBq1kNbPdx3lF0zQT4xIVmCCklA+DaQ==
dependencies:
"@ethersproject/abstract-provider" ">=5.0.0-beta.131"
"@ethersproject/abstract-signer" ">=5.0.0-beta.132"
"@ethersproject/address" ">=5.0.0-beta.128"
"@ethersproject/bignumber" ">=5.0.0-beta.130"
"@ethersproject/bytes" ">=5.0.0-beta.129"
"@ethersproject/constants" ">=5.0.0-beta.128"
"@ethersproject/hash" ">=5.0.0-beta.128"
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/networks" ">=5.0.0-beta.129"
"@ethersproject/properties" ">=5.0.0-beta.131"
"@ethersproject/random" ">=5.0.0-beta.128"
"@ethersproject/rlp" ">=5.0.0-beta.126"
"@ethersproject/strings" ">=5.0.0-beta.130"
"@ethersproject/transactions" ">=5.0.0-beta.128"
"@ethersproject/web" ">=5.0.0-beta.129"
"@ethersproject/random@>=5.0.0-beta.128":
version "5.0.0-beta.133"
resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.0.0-beta.133.tgz#5a7a05bc8bba446995eee77008ec2d48c938071f"
integrity sha512-walmaJK9MWy02I0SAHmv5Dg8His0Vn4x/ehqlu081z5gpm0WRo9H+3tlhaHQzAI0aK6B3mRV0bsG+PvWRh41Jg==
dependencies:
"@ethersproject/bytes" ">=5.0.0-beta.129"
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/rlp@>=5.0.0-beta.126":
version "5.0.0-beta.131"
resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.0.0-beta.131.tgz#4a0c0c314e26ed7f01be6bca16308d629a8022d2"
@ -1170,6 +1375,25 @@
dependencies:
"@ethersproject/bytes" ">=5.0.0-beta.129"
"@ethersproject/signing-key@>=5.0.0-beta.129":
version "5.0.0-beta.135"
resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.0.0-beta.135.tgz#f739e800aad9e01b77a8ec2c353b9b66ce5738fa"
integrity sha512-D4w5svi8F8eYs+LTuroKzOR8le6ZKtmH/mDmtuz15vz3XdOkLPGVne5mqqqLJd8APBnOEDtsAqmg7ZCrAk8Mag==
dependencies:
"@ethersproject/bytes" ">=5.0.0-beta.129"
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/properties" ">=5.0.0-beta.131"
elliptic "6.5.2"
"@ethersproject/strings@>=5.0.0-beta.130":
version "5.0.0-beta.136"
resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.0.0-beta.136.tgz#053cbf4f9f96a7537cbc50300597f2d707907f51"
integrity sha512-Hb9RvTrgGcOavHvtQZz+AuijB79BO3g1cfF2MeMfCU9ID4j3mbZv/olzDMS2pK9r4aERJpAS94AmlWzCgoY2LQ==
dependencies:
"@ethersproject/bytes" ">=5.0.0-beta.129"
"@ethersproject/constants" ">=5.0.0-beta.128"
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/strings@^5.0.0-beta.125":
version "5.0.0-beta.135"
resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.0.0-beta.135.tgz#7754a805831383bf1838cd097d26c6135e96a745"
@ -1179,6 +1403,41 @@
"@ethersproject/constants" ">=5.0.0-beta.128"
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/transactions@>=5.0.0-beta.128":
version "5.0.0-beta.134"
resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.0.0-beta.134.tgz#2cce01ca0519beb60a3a85c078b086580fec834d"
integrity sha512-06VxNv6UHds153Ey3WJ2YDPReNkwmIm8fyuJOXRZ6IoYh5ns5CfR4fkmHSBtw7+/KIVjmRoMQZ4Yg/tcGmzz0A==
dependencies:
"@ethersproject/address" ">=5.0.0-beta.128"
"@ethersproject/bignumber" ">=5.0.0-beta.130"
"@ethersproject/bytes" ">=5.0.0-beta.129"
"@ethersproject/constants" ">=5.0.0-beta.128"
"@ethersproject/keccak256" ">=5.0.0-beta.127"
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/properties" ">=5.0.0-beta.131"
"@ethersproject/rlp" ">=5.0.0-beta.126"
"@ethersproject/signing-key" ">=5.0.0-beta.129"
"@ethersproject/units@^5.0.0-beta.132":
version "5.0.0-beta.132"
resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.0.0-beta.132.tgz#54c03c821e515a09ef79a22704ad57994ee66c45"
integrity sha512-3GZDup1uTydvqaP5wpwoRF36irp6kx/gd3buPG+aoGWLPCoPjyk76OiGoxNQKfEaynOdZ7zG2lM8WevlBDJ57g==
dependencies:
"@ethersproject/bignumber" ">=5.0.0-beta.130"
"@ethersproject/constants" ">=5.0.0-beta.128"
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/web@>=5.0.0-beta.129":
version "5.0.0-beta.135"
resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.0.0-beta.135.tgz#f2ddc5d7f3fbeda69f77c20aff271784f6c3ae5c"
integrity sha512-5g0jGtQvLeU/O0KTlp9cqm5PpuDUZzopJaO8LxZpFW9t+5uVuBcJe8uoA8z+OsAnsRKPc6bGBc9esrWEW0hKLw==
dependencies:
"@ethersproject/base64" ">=5.0.0-beta.126"
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/properties" ">=5.0.0-beta.131"
"@ethersproject/strings" ">=5.0.0-beta.130"
cross-fetch "3.0.4"
"@hapi/address@2.x.x":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
@ -1359,6 +1618,16 @@
"@types/istanbul-reports" "^1.1.1"
"@types/yargs" "^13.0.0"
"@jest/types@^25.1.0":
version "25.1.0"
resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.1.0.tgz#b26831916f0d7c381e11dbb5e103a72aed1b4395"
integrity sha512-VpOtt7tCrgvamWZh1reVsGADujKigBUFTi19mlRjqEGsE8qH4r3s+skY33dNdXOwyZIvuftZ5tqdF1IgsMejMA==
dependencies:
"@types/istanbul-lib-coverage" "^2.0.0"
"@types/istanbul-reports" "^1.1.1"
"@types/yargs" "^15.0.0"
chalk "^3.0.0"
"@ledgerhq/devices@^4.78.0":
version "4.78.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-4.78.0.tgz#149b572f0616096e2bd5eb14ce14d0061c432be6"
@ -1567,6 +1836,105 @@
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==
"@styled-system/background@^5.1.2":
version "5.1.2"
resolved "https://registry.yarnpkg.com/@styled-system/background/-/background-5.1.2.tgz#75c63d06b497ab372b70186c0bf608d62847a2ba"
integrity sha512-jtwH2C/U6ssuGSvwTN3ri/IyjdHb8W9X/g8Y0JLcrH02G+BW3OS8kZdHphF1/YyRklnrKrBT2ngwGUK6aqqV3A==
dependencies:
"@styled-system/core" "^5.1.2"
"@styled-system/border@^5.1.2":
version "5.1.2"
resolved "https://registry.yarnpkg.com/@styled-system/border/-/border-5.1.2.tgz#34c81c6110f638550f1dda535edb44a82ee9fe49"
integrity sha512-mSSxyQGXELdNSOlf4RqaOKsX+w6//zooR3p6qDj5Zgc5pIdEsJm63QLz6EST/6xBJwTX0Z1w4ExItdd6Q7rlTQ==
dependencies:
"@styled-system/core" "^5.1.2"
"@styled-system/color@^5.1.2":
version "5.1.2"
resolved "https://registry.yarnpkg.com/@styled-system/color/-/color-5.1.2.tgz#b8d6b4af481faabe4abca1a60f8daa4ccc2d9f43"
integrity sha512-1kCkeKDZkt4GYkuFNKc7vJQMcOmTl3bJY3YBUs7fCNM6mMYJeT1pViQ2LwBSBJytj3AB0o4IdLBoepgSgGl5MA==
dependencies:
"@styled-system/core" "^5.1.2"
"@styled-system/core@^5.1.2":
version "5.1.2"
resolved "https://registry.yarnpkg.com/@styled-system/core/-/core-5.1.2.tgz#b8b7b86455d5a0514f071c4fa8e434b987f6a772"
integrity sha512-XclBDdNIy7OPOsN4HBsawG2eiWfCcuFt6gxKn1x4QfMIgeO6TOlA2pZZ5GWZtIhCUqEPTgIBta6JXsGyCkLBYw==
dependencies:
object-assign "^4.1.1"
"@styled-system/css@^5.0.0", "@styled-system/css@^5.1.4":
version "5.1.4"
resolved "https://registry.yarnpkg.com/@styled-system/css/-/css-5.1.4.tgz#fc51d0789a69b3831e00e6f8daf9f1d345eebdc3"
integrity sha512-79IFT37Kxb6dlbx/0hwIGOakNHkK5oU3cMypGziShnEK8WMgK/+vuAi4MHO7uLI+FZ5U8MGYvGY9Gtk0mBzxSg==
"@styled-system/flexbox@^5.1.2":
version "5.1.2"
resolved "https://registry.yarnpkg.com/@styled-system/flexbox/-/flexbox-5.1.2.tgz#077090f43f61c3852df63da24e4108087a8beecf"
integrity sha512-6hHV52+eUk654Y1J2v77B8iLeBNtc+SA3R4necsu2VVinSD7+XY5PCCEzBFaWs42dtOEDIa2lMrgL0YBC01mDQ==
dependencies:
"@styled-system/core" "^5.1.2"
"@styled-system/grid@^5.1.2":
version "5.1.2"
resolved "https://registry.yarnpkg.com/@styled-system/grid/-/grid-5.1.2.tgz#7165049877732900b99cd00759679fbe45c6c573"
integrity sha512-K3YiV1KyHHzgdNuNlaw8oW2ktMuGga99o1e/NAfTEi5Zsa7JXxzwEnVSDSBdJC+z6R8WYTCYRQC6bkVFcvdTeg==
dependencies:
"@styled-system/core" "^5.1.2"
"@styled-system/layout@^5.1.2":
version "5.1.2"
resolved "https://registry.yarnpkg.com/@styled-system/layout/-/layout-5.1.2.tgz#12d73e79887e10062f4dbbbc2067462eace42339"
integrity sha512-wUhkMBqSeacPFhoE9S6UF3fsMEKFv91gF4AdDWp0Aym1yeMPpqz9l9qS/6vjSsDPF7zOb5cOKC3tcKKOMuDCPw==
dependencies:
"@styled-system/core" "^5.1.2"
"@styled-system/position@^5.1.2":
version "5.1.2"
resolved "https://registry.yarnpkg.com/@styled-system/position/-/position-5.1.2.tgz#56961266566836f57a24d8e8e33ce0c1adb59dd3"
integrity sha512-60IZfMXEOOZe3l1mCu6sj/2NAyUmES2kR9Kzp7s2D3P4qKsZWxD1Se1+wJvevb+1TP+ZMkGPEYYXRyU8M1aF5A==
dependencies:
"@styled-system/core" "^5.1.2"
"@styled-system/shadow@^5.1.2":
version "5.1.2"
resolved "https://registry.yarnpkg.com/@styled-system/shadow/-/shadow-5.1.2.tgz#beddab28d7de03cd0177a87ac4ed3b3b6d9831fd"
integrity sha512-wqniqYb7XuZM7K7C0d1Euxc4eGtqEe/lvM0WjuAFsQVImiq6KGT7s7is+0bNI8O4Dwg27jyu4Lfqo/oIQXNzAg==
dependencies:
"@styled-system/core" "^5.1.2"
"@styled-system/should-forward-prop@^5.0.0":
version "5.1.4"
resolved "https://registry.yarnpkg.com/@styled-system/should-forward-prop/-/should-forward-prop-5.1.4.tgz#1d32d7f0942692319e1e1798aad95fb75df36967"
integrity sha512-WvKlXdbzz64QX8E66dlt/t+AsHhE5mJPyxMAufKeUKn5DSj+1w7CfLtwVH2oYje7XFcrcZOV9elzaeMWE0znTw==
dependencies:
"@emotion/is-prop-valid" "^0.8.1"
"@emotion/memoize" "^0.7.1"
styled-system "^5.1.4"
"@styled-system/space@^5.1.2":
version "5.1.2"
resolved "https://registry.yarnpkg.com/@styled-system/space/-/space-5.1.2.tgz#38925d2fa29a41c0eb20e65b7c3efb6e8efce953"
integrity sha512-+zzYpR8uvfhcAbaPXhH8QgDAV//flxqxSjHiS9cDFQQUSznXMQmxJegbhcdEF7/eNnJgHeIXv1jmny78kipgBA==
dependencies:
"@styled-system/core" "^5.1.2"
"@styled-system/typography@^5.1.2":
version "5.1.2"
resolved "https://registry.yarnpkg.com/@styled-system/typography/-/typography-5.1.2.tgz#65fb791c67d50cd2900d234583eaacdca8c134f7"
integrity sha512-BxbVUnN8N7hJ4aaPOd7wEsudeT7CxarR+2hns8XCX1zp0DFfbWw4xYa/olA0oQaqx7F1hzDg+eRaGzAJbF+jOg==
dependencies:
"@styled-system/core" "^5.1.2"
"@styled-system/variant@^5.1.4":
version "5.1.4"
resolved "https://registry.yarnpkg.com/@styled-system/variant/-/variant-5.1.4.tgz#7902de8e690b94e70b9b1026233feb38245398bf"
integrity sha512-4bI2AYQfWU/ljvWlysKU8T+6gsVx5xXEI/yBvg2De7Jd6o03ZQ9tsL3OJwbzyMkIKg+UZp7YG190txEOb8K6tg==
dependencies:
"@styled-system/core" "^5.1.2"
"@styled-system/css" "^5.1.4"
"@svgr/babel-plugin-add-jsx-attribute@^4.2.0":
version "4.2.0"
resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz#dadcb6218503532d6884b210e7f3c502caaa44b1"
@ -1717,6 +2085,11 @@
dependencies:
"@types/node" "*"
"@types/color-name@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
"@types/eslint-visitor-keys@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
@ -1770,6 +2143,14 @@
"@types/istanbul-lib-coverage" "*"
"@types/istanbul-lib-report" "*"
"@types/jest@^25.1.3":
version "25.1.3"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-25.1.3.tgz#9b0b5addebccfb631175870be8ba62182f1bc35a"
integrity sha512-jqargqzyJWgWAJCXX96LBGR/Ei7wQcZBvRv0PLEu9ZByMfcs23keUJrKv9FMR6YZf9YCbfqDqgmY+JUBsnqhrg==
dependencies:
jest-diff "^25.1.0"
pretty-format "^25.1.0"
"@types/json-schema@^7.0.3":
version "7.0.4"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339"
@ -1790,6 +2171,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.13.tgz#ccebcdb990bd6139cd16e84c39dc2fb1023ca90c"
integrity sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==
"@types/node@^13.7.4":
version "13.7.4"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.4.tgz#76c3cb3a12909510f52e5dc04a6298cdf9504ffd"
integrity sha512-oVeL12C6gQS/GAExndigSaLxTrKpQPxewx9bOcwfvJiJge4rr7wNaph4J+ns5hrmIV2as5qxqN8YKthn9qh0jw==
"@types/parse-json@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
@ -1805,6 +2191,13 @@
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8"
integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==
"@types/react-dom@^16.9.5":
version "16.9.5"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.5.tgz#5de610b04a35d07ffd8f44edad93a71032d9aaa7"
integrity sha512-BX6RQ8s9D+2/gDhxrj8OW+YD4R+8hj7FEM/OJHGNR0KipE1h1mSsf39YeyC81qafkq+N3rU3h3RFbLSwE5VqUg==
dependencies:
"@types/react" "*"
"@types/react@*":
version "16.9.17"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.17.tgz#58f0cc0e9ec2425d1441dd7b623421a867aa253e"
@ -1813,6 +2206,14 @@
"@types/prop-types" "*"
csstype "^2.2.0"
"@types/react@^16.9.21":
version "16.9.21"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.21.tgz#99e274e2ecfab6bb93920e918341daa3198b348d"
integrity sha512-xpmenCMeBwJRct8vmIfczlgdOXWIWASoOM857kxKfHlVQvDltRh7IFRVfGws79iO2jkNPXOeWREyKoClzhBaQA==
dependencies:
"@types/prop-types" "*"
csstype "^2.2.0"
"@types/stack-utils@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
@ -1837,6 +2238,13 @@
dependencies:
"@types/yargs-parser" "*"
"@types/yargs@^15.0.0":
version "15.0.3"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.3.tgz#41453a0bc7ab393e995d1f5451455638edbd2baf"
integrity sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==
dependencies:
"@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@^2.8.0":
version "2.14.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.14.0.tgz#c74447400537d4eb7aae1e31879ab43e6c662a8a"
@ -1880,14 +2288,22 @@
semver "^6.3.0"
tsutils "^3.17.1"
"@uniswap/sdk@^1.0.0-beta.4":
version "1.0.0-beta.4"
resolved "https://registry.yarnpkg.com/@uniswap/sdk/-/sdk-1.0.0-beta.4.tgz#d14a0ddc15e8de9173f3d97075e7ebbaa2570f1a"
integrity sha512-a2GnlZrmg0Qxr4HFIBPCKygLFxu5f/hhIOKYVm12hG8vzsCVh72DEqZkpaOSFH3o6LDez4p2KnPvmwZkWVUnRg==
"@uniswap/sdk@next":
version "2.0.0-beta.15"
resolved "https://registry.yarnpkg.com/@uniswap/sdk/-/sdk-2.0.0-beta.15.tgz#66487b8402fbc5b083c3744a890e64fb4024409c"
integrity sha512-Vj4r0EBr1eHaOV7OfwqVUjkbNsB93xRfJuCpEx2+OitDLWkM+arkSnw1jcixPZ7Dt+GTPXflgXGv3rB4s+ODog==
dependencies:
bignumber.js "^9.0.0"
ethers "^4.0.28"
lodash.clonedeepwith "^4.5.0"
"@ethersproject/address" "^5.0.0-beta.134"
"@ethersproject/contracts" "^5.0.0-beta.143"
"@ethersproject/keccak256" "^5.0.0-beta.131"
"@ethersproject/networks" "^5.0.0-beta.135"
"@ethersproject/providers" "^5.0.0-beta.153"
big.js "^5.2.2"
decimal.js-light "^2.5.0"
jsbi "^3.1.1"
tiny-invariant "^1.1.0"
tiny-warning "^1.0.3"
toformat "^2.0.0"
"@walletconnect/browser@^1.0.0-beta.42":
version "1.0.0-beta.42"
@ -2383,6 +2799,14 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
ansi-styles@^4.0.0, ansi-styles@^4.1.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359"
integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==
dependencies:
"@types/color-name" "^1.1.1"
color-convert "^2.0.1"
ansi-wrap@0.1.0, ansi-wrap@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf"
@ -2942,6 +3366,22 @@ babel-plugin-dynamic-import-node@2.3.0, babel-plugin-dynamic-import-node@^2.3.0:
dependencies:
object.assign "^4.1.0"
babel-plugin-emotion@^10.0.27:
version "10.0.27"
resolved "https://registry.yarnpkg.com/babel-plugin-emotion/-/babel-plugin-emotion-10.0.27.tgz#59001cf5de847c1d61f2079cd906a90a00d3184f"
integrity sha512-SUNYcT4FqhOqvwv0z1oeYhqgheU8qrceLojuHyX17ngo7WtWqN5I9l3IGHzf21Xraj465CVzF4IvOlAF+3ed0A==
dependencies:
"@babel/helper-module-imports" "^7.0.0"
"@emotion/hash" "0.7.4"
"@emotion/memoize" "0.7.4"
"@emotion/serialize" "^0.11.15"
babel-plugin-macros "^2.0.0"
babel-plugin-syntax-jsx "^6.18.0"
convert-source-map "^1.5.0"
escape-string-regexp "^1.0.5"
find-root "^1.1.0"
source-map "^0.5.7"
babel-plugin-istanbul@^5.1.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz#df4ade83d897a92df069c4d9a25cf2671293c854"
@ -2968,6 +3408,15 @@ babel-plugin-macros@2.7.1:
cosmiconfig "^6.0.0"
resolve "^1.12.0"
babel-plugin-macros@^2.0.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138"
integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==
dependencies:
"@babel/runtime" "^7.7.2"
cosmiconfig "^6.0.0"
resolve "^1.12.0"
babel-plugin-named-asset-import@^0.3.5:
version "0.3.5"
resolved "https://registry.yarnpkg.com/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.5.tgz#d3fa1a7f1f4babd4ed0785b75e2f926df0d70d0d"
@ -3462,11 +3911,6 @@ bignumber.js@^8.1.1:
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-8.1.1.tgz#4b072ae5aea9c20f6730e4e5d529df1271c4d885"
integrity sha512-QD46ppGintwPGuL1KqmwhR0O+N2cZUg8JG/VzwI2e28sM9TqHjQB10lI4QAaMHVbLzwVLLAwEglpKPViWX+5NQ==
bignumber.js@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.0.tgz#805880f84a329b5eac6e7cb6f8274b6d82bdf075"
integrity sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==
bignumber.js@~8.0.2:
version "8.0.2"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-8.0.2.tgz#d8c4e1874359573b1ef03011a2d861214aeef137"
@ -4038,6 +4482,14 @@ chalk@^1.1.3:
strip-ansi "^3.0.0"
supports-color "^2.0.0"
chalk@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chardet@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
@ -4269,12 +4721,19 @@ color-convert@^1.3.0, color-convert@^1.9.0, color-convert@^1.9.1:
dependencies:
color-name "1.1.3"
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
dependencies:
color-name "~1.1.4"
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
color-name@^1.0.0:
color-name@^1.0.0, color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
@ -4591,6 +5050,14 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
cross-fetch@3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.4.tgz#7bef7020207e684a7638ef5f2f698e24d9eb283c"
integrity sha512-MSHgpjQqgbT/94D4CyADeNoYh52zMkCX4pcJvPP5WqPsLFMKjr2TCMg381ox5qI0ii2dPwaLx/00477knXqXVw==
dependencies:
node-fetch "2.6.0"
whatwg-fetch "3.0.0"
cross-fetch@^2.1.0, cross-fetch@^2.1.1:
version "2.2.3"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-2.2.3.tgz#e8a0b3c54598136e037f8650f8e823ccdfac198e"
@ -4854,7 +5321,7 @@ cssstyle@^1.0.0, cssstyle@^1.1.1:
dependencies:
cssom "0.3.x"
csstype@^2.2.0:
csstype@^2.2.0, csstype@^2.5.7:
version "2.6.8"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.8.tgz#0fb6fc2417ffd2816a418c9336da74d7f07db431"
integrity sha512-msVS9qTuMT5zwAGCVm4mxfrZ18BNc6Csd0oJAtiFMZ1FAx1CCvy2+5MDmYoix63LM/6NDbNtodCiGYGmFgO0dA==
@ -4926,6 +5393,11 @@ decamelize@^1.1.1, decamelize@^1.2.0:
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
decimal.js-light@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.0.tgz#ca7faf504c799326df94b0ab920424fdfc125348"
integrity sha512-b3VJCbd2hwUpeRGG3Toob+CRo8W22xplipNhP3tN7TSVB/cyMX71P1vM2Xjc9H74uV6dS2hDDmo/rHq8L87Upg==
decode-uri-component@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
@ -5180,6 +5652,11 @@ diff-sequences@^24.9.0:
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5"
integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==
diff-sequences@^25.1.0:
version "25.1.0"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.1.0.tgz#fd29a46f1c913fd66c22645dc75bffbe43051f32"
integrity sha512-nFIfVk5B/NStCsJ+zaPO4vYuLjlzQ6uFvPxzYyHlejNZ/UGa7G/n7peOXVrVNvRuyfstt+mZQYGpjxg9Z6N8Kw==
diffie-hellman@^5.0.0:
version "5.0.3"
resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
@ -6275,10 +6752,10 @@ ethers@4.0.0-beta.3:
uuid "2.0.1"
xmlhttprequest "1.8.0"
ethers@^4.0.28, ethers@~4.0.4:
version "4.0.41"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.41.tgz#a0cff526f08c2e08c525cf82ef4483f6333b8000"
integrity sha512-QpW2CPZajquwiA7rVDozMksOuvdUBKIruamAakt0++EKBB/VWtLB9zSRZDInLDpp9fZYgOT/0trPD38r6CzLIg==
ethers@^4.0.44:
version "4.0.44"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.44.tgz#f2608cbc0b4d099b7e10a01c0efc3a1037013b4e"
integrity sha512-kCkMPkpYjBkxzqjcuYUfDY7VHDbf5EXnfRPUOazdqdf59SvXaT+w5lgauxLlk1UjxnAiNfeNS87rkIXnsTaM7Q==
dependencies:
aes-js "3.0.0"
bn.js "^4.4.0"
@ -6290,10 +6767,10 @@ ethers@^4.0.28, ethers@~4.0.4:
uuid "2.0.1"
xmlhttprequest "1.8.0"
ethers@^4.0.44:
version "4.0.44"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.44.tgz#f2608cbc0b4d099b7e10a01c0efc3a1037013b4e"
integrity sha512-kCkMPkpYjBkxzqjcuYUfDY7VHDbf5EXnfRPUOazdqdf59SvXaT+w5lgauxLlk1UjxnAiNfeNS87rkIXnsTaM7Q==
ethers@~4.0.4:
version "4.0.41"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.41.tgz#a0cff526f08c2e08c525cf82ef4483f6333b8000"
integrity sha512-QpW2CPZajquwiA7rVDozMksOuvdUBKIruamAakt0++EKBB/VWtLB9zSRZDInLDpp9fZYgOT/0trPD38r6CzLIg==
dependencies:
aes-js "3.0.0"
bn.js "^4.4.0"
@ -6705,6 +7182,11 @@ find-cache-dir@^3.0.0:
make-dir "^3.0.0"
pkg-dir "^4.1.0"
find-root@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==
find-up@3.0.0, find-up@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
@ -7415,6 +7897,11 @@ has-flag@^3.0.0:
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
has-symbol-support-x@^1.4.1:
version "1.4.2"
resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455"
@ -8530,6 +9017,16 @@ jest-diff@^24.9.0:
jest-get-type "^24.9.0"
pretty-format "^24.9.0"
jest-diff@^25.1.0:
version "25.1.0"
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.1.0.tgz#58b827e63edea1bc80c1de952b80cec9ac50e1ad"
integrity sha512-nepXgajT+h017APJTreSieh4zCqnSHEJ1iT8HDlewu630lSJ4Kjjr9KNzm+kzGwwcpsDE6Snx1GJGzzsefaEHw==
dependencies:
chalk "^3.0.0"
diff-sequences "^25.1.0"
jest-get-type "^25.1.0"
pretty-format "^25.1.0"
jest-docblock@^24.3.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.9.0.tgz#7970201802ba560e1c4092cc25cbedf5af5a8ce2"
@ -8585,6 +9082,11 @@ jest-get-type@^24.9.0:
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e"
integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==
jest-get-type@^25.1.0:
version "25.1.0"
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.1.0.tgz#1cfe5fc34f148dc3a8a3b7275f6b9ce9e2e8a876"
integrity sha512-yWkBnT+5tMr8ANB6V+OjmrIJufHtCAqI5ic2H40v+tRqxDmE0PGnIiTyvRWFOMtmVHYpwRqyazDbTnhpjsGvLw==
jest-haste-map@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d"
@ -8883,6 +9385,11 @@ js-yaml@^3.13.1:
argparse "^1.0.7"
esprima "^4.0.0"
jsbi@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-3.1.1.tgz#8ea18b3e08d102c6cc09acaa9a099921d775f4fa"
integrity sha512-+HQESPaV0mRiH614z4JPVPAftcRC2p53x92lySPzUzFwJbJTMpzHz8OYUkcXPN3fOcHUe0NdVcHnCtX/1+eCrA==
jsbn@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
@ -9479,11 +9986,6 @@ lodash._reinterpolate@^3.0.0:
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
lodash.clonedeepwith@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.clonedeepwith/-/lodash.clonedeepwith-4.5.0.tgz#6ee30573a03a1a60d670a62ef33c10cf1afdbdd4"
integrity sha1-buMFc6A6GmDWcKYu8zwQzxr9vdQ=
lodash.flatmap@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz#ef8cbf408f6e48268663345305c6acc0b778702e"
@ -10160,6 +10662,11 @@ node-fetch@2.1.2:
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5"
integrity sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=
node-fetch@2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
node-fetch@^1.0.1, node-fetch@~1.7.1:
version "1.7.3"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
@ -11801,6 +12308,16 @@ pretty-format@^24.9.0:
ansi-styles "^3.2.0"
react-is "^16.8.4"
pretty-format@^25.1.0:
version "25.1.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.1.0.tgz#ed869bdaec1356fc5ae45de045e2c8ec7b07b0c8"
integrity sha512-46zLRSGLd02Rp+Lhad9zzuNZ+swunitn8zIpfD2B4OPCRLXbM87RJT2aBLBWYOznNUML/2l/ReMyWNC80PJBUQ==
dependencies:
"@jest/types" "^25.1.0"
ansi-regex "^5.0.0"
ansi-styles "^4.0.0"
react-is "^16.12.0"
pretty-hrtime@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1"
@ -12224,7 +12741,7 @@ react-i18next@^10.7.0:
"@babel/runtime" "^7.3.1"
html-parse-stringify2 "2.0.1"
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4:
react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4:
version "16.12.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c"
integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==
@ -12480,6 +12997,13 @@ realpath-native@^1.1.0:
dependencies:
util.promisify "^1.0.0"
rebass@^4.0.7:
version "4.0.7"
resolved "https://registry.yarnpkg.com/rebass/-/rebass-4.0.7.tgz#0a84e5558750c1f416c3baf41ec4c7fc8d64a98a"
integrity sha512-GJot6j6Qcr7jk1QIgf9qBoud75CGRpN8pGcEo98TSp4KNSWV01ZLvGwFKGI35oEBuNs+lpEd3+pnwkQUTSFytg==
dependencies:
reflexbox "^4.0.6"
rechoir@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
@ -12494,6 +13018,17 @@ recursive-readdir@2.2.2:
dependencies:
minimatch "3.0.4"
reflexbox@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/reflexbox/-/reflexbox-4.0.6.tgz#fc756d2cc1ca493baf9b96bb27dd640ad8154cf1"
integrity sha512-UNUL4YoJEXAPjRKHuty1tuOk+LV1nDJ2KYViDcH7lYm5yU3AQ+EKNXxPU3E14bQNK/pE09b1hYl+ZKdA94tWLQ==
dependencies:
"@emotion/core" "^10.0.0"
"@emotion/styled" "^10.0.0"
"@styled-system/css" "^5.0.0"
"@styled-system/should-forward-prop" "^5.0.0"
styled-system "^5.0.0"
regenerate-unicode-properties@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e"
@ -13873,6 +14408,25 @@ styled-components@^4.2.0:
stylis-rule-sheet "^0.0.10"
supports-color "^5.5.0"
styled-system@^5.0.0, styled-system@^5.1.4:
version "5.1.4"
resolved "https://registry.yarnpkg.com/styled-system/-/styled-system-5.1.4.tgz#953003bbda659092e5630e23da2ab7e855c65879"
integrity sha512-b1EdfZ41NDcR6vnvZauylhpFvSjsFl1yyQEUA+v3rLjcKdM//EIFY195Nh3YLwgj+hWIWsG0Tk1Kl0tq1xLw8Q==
dependencies:
"@styled-system/background" "^5.1.2"
"@styled-system/border" "^5.1.2"
"@styled-system/color" "^5.1.2"
"@styled-system/core" "^5.1.2"
"@styled-system/flexbox" "^5.1.2"
"@styled-system/grid" "^5.1.2"
"@styled-system/layout" "^5.1.2"
"@styled-system/position" "^5.1.2"
"@styled-system/shadow" "^5.1.2"
"@styled-system/space" "^5.1.2"
"@styled-system/typography" "^5.1.2"
"@styled-system/variant" "^5.1.4"
object-assign "^4.1.1"
stylehacks@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5"
@ -13911,6 +14465,13 @@ supports-color@^6.1.0:
dependencies:
has-flag "^3.0.0"
supports-color@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1"
integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==
dependencies:
has-flag "^4.0.0"
sver-compat@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/sver-compat/-/sver-compat-1.5.0.tgz#3cf87dfeb4d07b4a3f14827bc186b3fd0c645cd8"
@ -14159,6 +14720,11 @@ tiny-invariant@^1.0.2, tiny-invariant@^1.0.6:
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.6.tgz#b3f9b38835e36a41c843a3b0907a5a7b3755de73"
integrity sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==
tiny-invariant@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
@ -14248,6 +14814,11 @@ to-through@^2.0.0:
dependencies:
through2 "^2.0.3"
toformat@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/toformat/-/toformat-2.0.0.tgz#7a043fd2dfbe9021a4e36e508835ba32056739d8"
integrity sha512-03SWBVop6nU8bpyZCx7SodpYznbZF5R4ljwNLBcTQzKOD9xuihRo/psX58llS1BMFhhAI08H3luot5GoXJz2pQ==
toggle-selection@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
@ -14372,6 +14943,11 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typescript@^3.7.5:
version "3.7.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae"
integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==
typewise-core@^1.2, typewise-core@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/typewise-core/-/typewise-core-1.2.0.tgz#97eb91805c7f55d2f941748fa50d315d991ef195"
@ -15321,7 +15897,7 @@ whatwg-fetch@2.0.4:
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f"
integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==
whatwg-fetch@>=0.10.0, whatwg-fetch@^3.0.0:
whatwg-fetch@3.0.0, whatwg-fetch@>=0.10.0, whatwg-fetch@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb"
integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==