Compare commits

...

30 Commits

Author SHA1 Message Date
Gökhan Çoban
3fbc4e34f4 typo: use main branch for pull requests (#1355) 2021-04-03 19:17:38 -05:00
Moody Salem
b964953daf fix(walletconnect): use a dedicated walletconnect bridge 2021-03-31 12:36:56 -05:00
Moody Salem
649fd9c845 fixes https://github.com/Uniswap/uniswap-interface/issues/1351 (#1352) 2021-03-31 12:26:03 -05:00
Ian Lapham
6347e63a15 update unsupported list (#1346) 2021-03-28 16:11:18 -04:00
Moody Salem
bdcb9a8a0a fix(google analytics): anonymize IP in hits sent to google analytics
https://developers.google.com/analytics/devguides/collection/analyticsjs/ip-anonymization
2021-03-26 16:40:16 -05:00
Moody Salem
8d90bb7a39 fix(google analytics): don't set user cookies 2021-03-26 16:32:13 -05:00
Kun
d70b456855 Add Pin to Crust workflow (#1342) 2021-03-26 10:03:25 -05:00
Luke Donato
fbb797fa54 Move single hop toggle GA event (#1344)
* Move single hop toggle GA event

Move GA event from setSingleHopOnly hook to toggle function

* Fix code style issues with ESLint

* refactor ternary operator out

Co-authored-by: Lint Action <lint-action@samuelmeuli.com>
2021-03-26 10:02:07 -05:00
Jordan Frankfurt
8ace518311 stop destructuring merkle drop response object (#1338)
stop destructuring merkle drop response object

Co-authored-by: Jordan Frankfurt <layup-entropy@protonmail.com>
2021-03-18 11:57:13 -04:00
Jordan Frankfurt
67c776c995 fix(merkle drop): make claim a post request 2021-03-18 01:46:11 -04:00
Rand Seay
719754c46c fix(info link): Update view pair analytics link
fixes #1299
2021-03-16 13:55:38 -05:00
Ian Lapham
9170af888e handle dismiss (#1328) 2021-03-11 18:08:41 -05:00
Ian Lapham
b258f557d1 improvement(lists): add BA SEC tokens to unsupported list (#1327)
* show hidden search results by default

* update break styles

* optimize filter, use debounce on input

* increase debounce time

* add ba association list
2021-02-25 14:08:17 -05:00
Hyperion
9d8c7f8e12 fix: modals stealing focus across frames (#1326) 2021-02-24 14:59:04 -06:00
Jordan Frankfurt
9c44e61e23 unregisters all installed service workers (#1322) 2021-02-18 13:34:31 -05:00
Jordan Frankfurt
71db11b6ac Revert "Revert "feature(service worker): add offline support (#1319)" (#1320)" (#1321)
This reverts commit db3328c8d9.
2021-02-18 13:10:53 -05:00
Jordan Frankfurt
db3328c8d9 Revert "feature(service worker): add offline support (#1319)" (#1320)
This reverts commit 34dfb41a1e.
2021-02-18 13:03:32 -05:00
Jordan Frankfurt
34dfb41a1e feature(service worker): add offline support (#1319) 2021-02-17 14:43:49 -05:00
Moody Salem
e77fcd21dc Update index.ts 2021-02-13 20:03:05 -06:00
Jordan Frankfurt
0b7846ee1d fix(discord): correct links (#1315) 2021-02-10 10:56:42 -05:00
Ian Lapham
f450d34d69 feat(transactions): enable button to add tokens to metamask (#1311)
* start on adding button for watching tokens

* add tokens to metamask

* add confirmation view

* reset modal view
2021-02-09 18:55:38 -05:00
Ian Lapham
76ab349b9e show hidden search results by default (#1310)
* show hidden search results by default

* update break styles

* optimize filter, use debounce on input

* increase debounce time
2021-02-09 17:51:27 -05:00
Jordan Frankfurt
5c3c1c67f5 run npx browserslist@latest --update-db (#1303) 2021-02-09 11:49:51 -05:00
Moody Salem
9efd5da1f7 change branch in workflows 2021-02-03 12:56:02 -06:00
Jordan Frankfurt
8fd894f2d1 fix a prominent instance (#1304) 2021-02-03 12:34:46 -05:00
Ian Lapham
cc22183388 fix(modal cutoff, lists): make modal scrollable on small screens, fix typo, auto update all lists (#1301)
* update all lists, not just active ones

* fix typo
2021-02-01 12:37:18 -05:00
Moody Salem
9175dd10cc remove cloudflare insights 2021-02-01 09:54:06 -06:00
Moody Salem
bbd50f066d fixes https://github.com/Uniswap/uniswap-interface/issues/1214 2021-02-01 09:44:47 -06:00
Ian Lapham
2291e3ec20 improvement(lists): enable Gemini by default (#1276)
* update import flow and style tweaks

* update tests

* Update tsconfig.json

* add gemini

* enable gemini
2021-01-21 17:34:15 -07:00
Ian Lapham
28d8f0b0bb Improvement(token import): update import flow and style tweaks (#1273)
* update import flow and style tweaks

* update tests

* Update tsconfig.json
2021-01-20 23:22:32 -07:00
50 changed files with 769 additions and 863 deletions

3
.env
View File

@@ -1,2 +1,3 @@
REACT_APP_CHAIN_ID="1"
REACT_APP_NETWORK_URL="https://mainnet.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847"
REACT_APP_NETWORK_URL="https://mainnet.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847"
REACT_APP_WALLETCONNECT_BRIDGE_URL="https://uniswap.bridge.walletconnect.org"

View File

@@ -1,7 +1,7 @@
blank_issues_enabled: false
contact_links:
- name: Support
url: https://discord.gg/EwFs3Pp
url: https://discord.gg/FCfyBSbCU5
about: Please ask and answer questions here
- name: List a token
url: https://github.com/Uniswap/default-token-list#adding-a-token

View File

@@ -3,10 +3,10 @@ name: Lint
on:
push:
branches:
- master
- main
pull_request_target:
branches:
- master
- main
jobs:
run-linters:

View File

@@ -54,6 +54,14 @@ jobs:
pinata-api-key: ${{ secrets.PINATA_API_KEY }}
pinata-secret-api-key: ${{ secrets.PINATA_API_SECRET_KEY }}
- name: Pin to Crust
uses: crustio/ipfs-crust-action@v1.0.8
continue-on-error: true
timeout-minutes: 2
with:
cid: ${{ steps.upload.outputs.hash }}
seeds: ${{ secrets.CRUST_SEEDS }}
- name: Convert CIDv0 to CIDv1
id: convert_cidv0
uses: uniswap/convert-cidv0-cidv1@v1.0.0

View File

@@ -2,10 +2,10 @@ name: Tests
on:
push:
branches:
- master
- main
pull_request:
branches:
- master
- main
jobs:
integration-tests:

View File

@@ -12,7 +12,7 @@ An open source interface for Uniswap -- a protocol for decentralized exchange of
- Twitter: [@UniswapProtocol](https://twitter.com/UniswapProtocol)
- Reddit: [/r/Uniswap](https://www.reddit.com/r/Uniswap/)
- Email: [contact@uniswap.org](mailto:contact@uniswap.org)
- Discord: [Uniswap](https://discord.gg/Y7TF6QA)
- Discord: [Uniswap](https://discord.gg/FCfyBSbCU5)
- Whitepaper: [Link](https://hackmd.io/C-DvwDSfSxuh-Gd4WKE_ig)
## Accessing the Uniswap Interface
@@ -56,7 +56,7 @@ The interface will not work on other networks.
## Contributions
**Please open all pull requests against the `master` branch.**
**Please open all pull requests against the `main` branch.**
CI checks will run against all PRs.
## Accessing Uniswap Interface V1

View File

@@ -1,17 +0,0 @@
describe('Warning', () => {
beforeEach(() => {
cy.visit('/swap?outputCurrency=0x0a40f26d74274b7f22b28556a27b35d97ce08e0a')
})
it('Check that warning is displayed', () => {
cy.get('.token-warning-container').should('be.visible')
})
it('Check that warning hides after button dismissal', () => {
cy.get('.token-dismiss-button').should('be.disabled')
cy.get('.understand-checkbox').click()
cy.get('.token-dismiss-button').should('not.be.disabled')
cy.get('.token-dismiss-button').click()
cy.get('.token-warning-container').should('not.be.visible')
})
})

View File

@@ -82,18 +82,24 @@
"react-window": "^1.8.5",
"rebass": "^4.0.7",
"redux-localstorage-simple": "^2.3.1",
"serve": "^11.3.0",
"serve": "^11.3.2",
"start-server-and-test": "^1.11.0",
"styled-components": "^4.2.0",
"typescript": "^3.8.3",
"use-count-up": "^2.2.5",
"wcag-contrast": "^3.0.0"
"wcag-contrast": "^3.0.0",
"workbox-core": "^6.1.0",
"workbox-expiration": "^6.1.0",
"workbox-precaching": "^6.1.0",
"workbox-routing": "^6.1.0",
"workbox-strategies": "^6.1.0"
},
"resolutions": {
"@walletconnect/web3-provider": "1.1.1-alpha.0"
},
"scripts": {
"start": "react-scripts start",
"start:service-worker": "yarn build && yarn serve -s build",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject",
@@ -117,5 +123,8 @@
"last 1 safari version"
]
},
"license": "GPL-3.0-or-later"
"license": "GPL-3.0-or-later",
"dependencies": {
"@uniswap/default-token-list": "^2.0.0"
}
}

View File

@@ -39,6 +39,5 @@
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
<script defer src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon='{"token": "a141524a42594f028627f6004b23ff75"}'></script>
</body>
</html>

View File

@@ -1,6 +1,7 @@
{
"short_name": "Uniswap",
"name": "Uniswap",
"background_color": "#fff",
"display": "standalone",
"homepage_url": "https://app.uniswap.org",
"icons": [
{
"src": "./images/192x192_App_Icon.png",
@@ -16,7 +17,8 @@
}
],
"orientation": "portrait",
"display": "standalone",
"theme_color": "#ff007a",
"background_color": "#fff"
"name": "Uniswap",
"short_name": "Uniswap",
"start_url": ".",
"theme_color": "#ff007a"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -0,0 +1,30 @@
<svg width="225" height="225" viewBox="0 0 225 225" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M74.8125 190.529C65.7561 190.513 55.5298 183.748 51.9715 175.42L19.9417 100.456C16.3834 92.1277 20.8404 85.39 29.8968 85.4068L111.417 85.5579C120.473 85.5747 130.699 92.3395 134.258 100.668L166.288 175.632C169.846 183.96 165.389 190.697 156.332 190.681L74.8125 190.529Z" fill="#131313"/>
<path d="M92.1541 164.065C83.0977 164.049 72.8715 157.284 69.3132 148.956L28.3003 52.9672C24.7419 44.6391 29.199 37.9015 38.2554 37.9182L142.638 38.1117C151.695 38.1285 161.921 44.8933 165.479 53.2214L206.492 149.21C210.051 157.538 205.594 164.276 196.537 164.259L92.1541 164.065Z" fill="white"/>
<path d="M92.1541 164.065C83.0977 164.049 72.8715 157.284 69.3132 148.956L28.3003 52.9672C24.7419 44.6391 29.199 37.9015 38.2554 37.9182L142.638 38.1117C151.695 38.1285 161.921 44.8933 165.479 53.2214L206.492 149.21C210.051 157.538 205.594 164.276 196.537 164.259L92.1541 164.065Z" fill="url(#paint0_radial)"/>
<path d="M92.1541 164.065C83.0977 164.049 72.8715 157.284 69.3132 148.956L28.3003 52.9672C24.7419 44.6391 29.199 37.9015 38.2554 37.9182L142.638 38.1117C151.695 38.1285 161.921 44.8933 165.479 53.2214L206.492 149.21C210.051 157.538 205.594 164.276 196.537 164.259L92.1541 164.065Z" fill="url(#paint1_radial)"/>
<path d="M92.1541 164.065C83.0977 164.049 72.8715 157.284 69.3132 148.956L28.3003 52.9672C24.7419 44.6391 29.199 37.9015 38.2554 37.9182L142.638 38.1117C151.695 38.1285 161.921 44.8933 165.479 53.2214L206.492 149.21C210.051 157.538 205.594 164.276 196.537 164.259L92.1541 164.065Z" fill="url(#paint2_radial)"/>
<path d="M92.1541 164.065C83.0977 164.049 72.8715 157.284 69.3132 148.956L28.3003 52.9672C24.7419 44.6391 29.199 37.9015 38.2554 37.9182L142.638 38.1117C151.695 38.1285 161.921 44.8933 165.479 53.2214L206.492 149.21C210.051 157.538 205.594 164.276 196.537 164.259L92.1541 164.065Z" fill="url(#paint3_radial)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M92.958 165.95C82.7695 165.931 71.265 158.321 67.2619 148.952L26.2489 52.9632C22.2458 43.5941 27.26 36.0143 37.4485 36.0332L141.832 36.2266C152.02 36.2455 163.525 43.8559 167.528 53.225L208.541 149.214C212.544 158.583 207.53 166.163 197.341 166.144L92.958 165.95ZM71.3614 148.959C74.475 156.246 83.4229 162.166 91.3473 162.18L195.73 162.374C203.655 162.388 207.555 156.493 204.441 149.206L163.428 53.2174C160.315 45.9304 151.367 40.0111 143.442 39.9964L39.0592 39.803C31.1349 39.7883 27.2349 45.6837 30.3485 52.9708L71.3614 148.959Z" fill="#131313"/>
<path d="M68.565 53.3425C81.1781 53.3659 95.4205 62.7875 100.376 74.3862C105.332 85.985 99.1246 95.3687 86.5115 95.3454C73.8984 95.322 59.6559 85.9004 54.7001 74.3016C49.7443 62.7028 55.9518 53.3191 68.565 53.3425Z" fill="#131313"/>
<path d="M90.6891 104.981C103.302 105.004 117.545 114.425 122.5 126.024C127.456 137.623 121.249 147.007 108.636 146.983C96.0225 146.96 81.7801 137.538 76.8243 125.94C71.8685 114.341 78.076 104.957 90.6891 104.981Z" fill="#131313"/>
<path d="M147.538 105.142C160.151 105.166 174.394 114.587 179.349 126.186C184.305 137.785 178.098 147.168 165.485 147.145C152.871 147.122 138.629 137.7 133.673 126.101C128.717 114.503 134.925 105.119 147.538 105.142Z" fill="#131313"/>
<defs>
<radialGradient id="paint0_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(134.41 68.3006) rotate(-33.9533) scale(90.6795 83.3208)">
<stop offset="0.661458" stop-color="#C4FCF8"/>
<stop offset="1" stop-color="white" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint1_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(42.7873 129.218) rotate(-24.1606) scale(213.359 196.045)">
<stop stop-color="#FF0099" stop-opacity="0.9"/>
<stop offset="0.770833" stop-color="white" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint2_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(176.854 148.655) rotate(-53.4908) scale(107.342 98.6309)">
<stop stop-color="#FFEC43"/>
<stop offset="0.805707" stop-color="#FFF6A8" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint3_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(57.5443 53.4752) rotate(20.3896) scale(137.027 125.907)">
<stop offset="0.125" stop-color="#5886FE" stop-opacity="0.46"/>
<stop offset="0.673044" stop-color="white" stop-opacity="0"/>
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -18,6 +18,10 @@ export const LightCard = styled(Card)`
background-color: ${({ theme }) => theme.bg1};
`
export const LightGreyCard = styled(Card)`
background-color: ${({ theme }) => theme.bg2};
`
export const GreyCard = styled(Card)`
background-color: ${({ theme }) => theme.bg3};
`

View File

@@ -7,7 +7,7 @@ import useHttpLocations from '../../hooks/useHttpLocations'
import { WrappedTokenInfo } from '../../state/lists/hooks'
import Logo from '../Logo'
const getTokenLogoURL = (address: string) =>
export const getTokenLogoURL = (address: string) =>
`https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/${address}/logo.png`
const StyledEthereumLogo = styled.img<{ size: string }>`
@@ -43,7 +43,6 @@ export default function CurrencyLogo({
if (currency instanceof WrappedTokenInfo) {
return [...uriLocations, getTokenLogoURL(currency.address)]
}
return [getTokenLogoURL(currency.address)]
}
return []

View File

@@ -83,7 +83,11 @@ const HeaderControls = styled.div`
const HeaderElement = styled.div`
display: flex;
align-items: center;
gap: 8px;
/* addresses safari's lack of support for "gap" */
& > *:not(:first-child) {
margin-left: 8px;
}
${({ theme }) => theme.mediaWidth.upToMedium`
flex-direction: row-reverse;

View File

@@ -117,7 +117,7 @@ export default function Menu() {
<Code size={14} />
Code
</MenuItem>
<MenuItem id="link" href="https://discord.gg/EwFs3Pp">
<MenuItem id="link" href="https://discord.gg/FCfyBSbCU5">
<MessageCircle size={14} />
Discord
</MenuItem>

View File

@@ -115,7 +115,13 @@ export default function Modal({
{fadeTransition.map(
({ item, key, props }) =>
item && (
<StyledDialogOverlay key={key} style={props} onDismiss={onDismiss} initialFocusRef={initialFocusRef}>
<StyledDialogOverlay
key={key}
style={props}
onDismiss={onDismiss}
initialFocusRef={initialFocusRef}
unstable_lockFocusAcrossFrames={false}
>
<StyledDialogContent
{...(isMobile
? {

View File

@@ -9,7 +9,7 @@ import { useCurrencyBalance } from '../../state/wallet/hooks'
import { TYPE } from '../../theme'
import { useIsUserAddedToken, useAllInactiveTokens } from '../../hooks/Tokens'
import Column from '../Column'
import { RowFixed } from '../Row'
import { RowFixed, RowBetween } from '../Row'
import CurrencyLogo from '../CurrencyLogo'
import { MouseoverTooltip } from '../Tooltip'
import { MenuItem } from './styleds'
@@ -17,6 +17,10 @@ import Loader from '../Loader'
import { isTokenOnList } from '../../utils'
import ImportRow from './ImportRow'
import { wrappedCurrency } from 'utils/wrappedCurrency'
import { LightGreyCard } from 'components/Card'
import TokenListLogo from '../../assets/svg/tokenlist.svg'
import QuestionHelper from 'components/QuestionHelper'
import useTheme from 'hooks/useTheme'
function currencyKey(currency: Currency): string {
return currency instanceof Token ? currency.address : currency === ETHER ? 'ETHER' : ''
@@ -43,6 +47,14 @@ const Tag = styled.div`
margin-right: 4px;
`
const FixedContentRow = styled.div`
padding: 4px 20px;
height: 56px;
display: grid;
grid-gap: 16px;
align-items: center;
`
function Balance({ balance }: { balance: CurrencyAmount }) {
return <StyledBalanceText title={balance.toExact()}>{balance.toSignificant(4)}</StyledBalanceText>
}
@@ -52,6 +64,10 @@ const TagContainer = styled.div`
justify-content: flex-end;
`
const TokenListLogoWrapper = styled.img`
height: 20px;
`
function TokenTags({ currency }: { currency: Currency }) {
if (!(currency instanceof WrappedTokenInfo)) {
return <span />
@@ -136,7 +152,8 @@ export default function CurrencyList({
fixedListRef,
showETH,
showImportView,
setImportToken
setImportToken,
breakIndex
}: {
height: number
currencies: Currency[]
@@ -147,10 +164,18 @@ export default function CurrencyList({
showETH: boolean
showImportView: () => void
setImportToken: (token: Token) => void
breakIndex: number | undefined
}) {
const itemData = useMemo(() => (showETH ? [Currency.ETHER, ...currencies] : currencies), [currencies, showETH])
const itemData: (Currency | undefined)[] = useMemo(() => {
let formatted: (Currency | undefined)[] = showETH ? [Currency.ETHER, ...currencies] : currencies
if (breakIndex !== undefined) {
formatted = [...formatted.slice(0, breakIndex), undefined, ...formatted.slice(breakIndex, formatted.length)]
}
return formatted
}, [breakIndex, currencies, showETH])
const { chainId } = useActiveWeb3React()
const theme = useTheme()
const inactiveTokens: {
[address: string]: Token
@@ -167,6 +192,24 @@ export default function CurrencyList({
const showImport = inactiveTokens && token && Object.keys(inactiveTokens).includes(token.address)
if (index === breakIndex || !data) {
return (
<FixedContentRow style={style}>
<LightGreyCard padding="8px 12px" borderRadius="8px">
<RowBetween>
<RowFixed>
<TokenListLogoWrapper src={TokenListLogo} />
<TYPE.main ml="6px" fontSize="12px" color={theme.text1}>
Expanded results from inactive Token Lists
</TYPE.main>
</RowFixed>
<QuestionHelper text="Tokens from inactive lists. Import specific tokens below or click 'Manage' to activate more lists." />
</RowBetween>
</LightGreyCard>
</FixedContentRow>
)
}
if (showImport && token) {
return (
<ImportRow
@@ -189,7 +232,17 @@ export default function CurrencyList({
)
}
},
[chainId, inactiveTokens, onCurrencySelect, otherCurrency, selectedCurrency, setImportToken, showImportView]
[
chainId,
inactiveTokens,
onCurrencySelect,
otherCurrency,
selectedCurrency,
setImportToken,
showImportView,
breakIndex,
theme.text1
]
)
const itemKey = useCallback((index: number, data: any) => currencyKey(data[index]), [])

View File

@@ -12,7 +12,7 @@ import Column from '../Column'
import Row, { RowBetween, RowFixed } from '../Row'
import CommonBases from './CommonBases'
import CurrencyList from './CurrencyList'
import { filterTokens } from './filtering'
import { filterTokens, useSortedTokensByQuery } from './filtering'
import { useTokenComparator } from './sorting'
import { PaddedColumn, SearchInput, Separator } from './styleds'
import AutoSizer from 'react-virtualized-auto-sizer'
@@ -22,7 +22,7 @@ import { useOnClickOutside } from 'hooks/useOnClickOutside'
import useTheme from 'hooks/useTheme'
import ImportRow from './ImportRow'
import { Edit } from 'react-feather'
import { ButtonLight } from 'components/Button'
import useDebounce from 'hooks/useDebounce'
const ContentWrapper = styled(Column)`
width: 100%;
@@ -71,14 +71,15 @@ export function CurrencySearch({
const fixedList = useRef<FixedSizeList>()
const [searchQuery, setSearchQuery] = useState<string>('')
const debouncedQuery = useDebounce(searchQuery, 200)
const [invertSearchOrder] = useState<boolean>(false)
const allTokens = useAllTokens()
// const inactiveTokens: Token[] | undefined = useFoundOnInactiveList(searchQuery)
// if they input an address, use it
const isAddressSearch = isAddress(searchQuery)
const searchToken = useToken(searchQuery)
const isAddressSearch = isAddress(debouncedQuery)
const searchToken = useToken(debouncedQuery)
const searchTokenIsAdded = useIsUserAddedToken(searchToken)
useEffect(() => {
@@ -92,46 +93,21 @@ export function CurrencySearch({
}, [isAddressSearch])
const showETH: boolean = useMemo(() => {
const s = searchQuery.toLowerCase().trim()
const s = debouncedQuery.toLowerCase().trim()
return s === '' || s === 'e' || s === 'et' || s === 'eth'
}, [searchQuery])
}, [debouncedQuery])
const tokenComparator = useTokenComparator(invertSearchOrder)
const filteredTokens: Token[] = useMemo(() => {
return filterTokens(Object.values(allTokens), searchQuery)
}, [allTokens, searchQuery])
return filterTokens(Object.values(allTokens), debouncedQuery)
}, [allTokens, debouncedQuery])
const filteredSortedTokens: Token[] = useMemo(() => {
const sorted = filteredTokens.sort(tokenComparator)
const symbolMatch = searchQuery
.toLowerCase()
.split(/\s+/)
.filter(s => s.length > 0)
const sortedTokens: Token[] = useMemo(() => {
return filteredTokens.sort(tokenComparator)
}, [filteredTokens, tokenComparator])
if (symbolMatch.length > 1) {
return sorted
}
return [
// sort any exact symbol matches first
...sorted.filter(token => token.symbol?.toLowerCase() === symbolMatch[0]),
// sort by tokens whos symbols start with search substrng
...sorted.filter(
token =>
token.symbol?.toLowerCase().startsWith(searchQuery.toLowerCase().trim()) &&
token.symbol?.toLowerCase() !== symbolMatch[0]
),
// rest that dont match upove
...sorted.filter(
token =>
!token.symbol?.toLowerCase().startsWith(searchQuery.toLowerCase().trim()) &&
token.symbol?.toLowerCase() !== symbolMatch[0]
)
]
}, [filteredTokens, searchQuery, tokenComparator])
const filteredSortedTokens = useSortedTokensByQuery(sortedTokens, debouncedQuery)
const handleCurrencySelect = useCallback(
(currency: Currency) => {
@@ -158,12 +134,12 @@ export function CurrencySearch({
const handleEnter = useCallback(
(e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
const s = searchQuery.toLowerCase().trim()
const s = debouncedQuery.toLowerCase().trim()
if (s === 'eth') {
handleCurrencySelect(ETHER)
} else if (filteredSortedTokens.length > 0) {
if (
filteredSortedTokens[0].symbol?.toLowerCase() === searchQuery.trim().toLowerCase() ||
filteredSortedTokens[0].symbol?.toLowerCase() === debouncedQuery.trim().toLowerCase() ||
filteredSortedTokens.length === 1
) {
handleCurrencySelect(filteredSortedTokens[0])
@@ -171,7 +147,7 @@ export function CurrencySearch({
}
}
},
[filteredSortedTokens, handleCurrencySelect, searchQuery]
[filteredSortedTokens, handleCurrencySelect, debouncedQuery]
)
// menu ui
@@ -180,15 +156,8 @@ export function CurrencySearch({
useOnClickOutside(node, open ? toggle : undefined)
// if no results on main list, show option to expand into inactive
const [showExpanded, setShowExpanded] = useState(false)
const inactiveTokens = useFoundOnInactiveList(searchQuery)
// reset expanded results on query reset
useEffect(() => {
if (searchQuery === '') {
setShowExpanded(false)
}
}, [setShowExpanded, searchQuery])
const inactiveTokens = useFoundOnInactiveList(debouncedQuery)
const filteredInactiveTokens: Token[] = useSortedTokensByQuery(inactiveTokens, debouncedQuery)
return (
<ContentWrapper>
@@ -220,7 +189,7 @@ export function CurrencySearch({
<Column style={{ padding: '20px 0', height: '100%' }}>
<ImportRow token={searchToken} showImportView={showImportView} setImportToken={setImportToken} />
</Column>
) : filteredSortedTokens?.length > 0 || (showExpanded && inactiveTokens && inactiveTokens.length > 0) ? (
) : filteredSortedTokens?.length > 0 || filteredInactiveTokens?.length > 0 ? (
<div style={{ flex: '1' }}>
<AutoSizer disableWidth>
{({ height }) => (
@@ -228,8 +197,9 @@ export function CurrencySearch({
height={height}
showETH={showETH}
currencies={
showExpanded && inactiveTokens ? filteredSortedTokens.concat(inactiveTokens) : filteredSortedTokens
filteredInactiveTokens ? filteredSortedTokens.concat(filteredInactiveTokens) : filteredSortedTokens
}
breakIndex={inactiveTokens && filteredSortedTokens ? filteredSortedTokens.length : undefined}
onCurrencySelect={handleCurrencySelect}
otherCurrency={otherSelectedCurrency}
selectedCurrency={selectedCurrency}
@@ -243,49 +213,10 @@ export function CurrencySearch({
) : (
<Column style={{ padding: '20px', height: '100%' }}>
<TYPE.main color={theme.text3} textAlign="center" mb="20px">
No results found in active lists.
No results found.
</TYPE.main>
{inactiveTokens &&
inactiveTokens.length > 0 &&
!(searchToken && !searchTokenIsAdded) &&
searchQuery.length > 1 &&
filteredSortedTokens?.length === 0 && (
// expand button in line with no results
<Row align="center" width="100%" justify="center">
<ButtonLight
width="fit-content"
borderRadius="12px"
padding="8px 12px"
onClick={() => setShowExpanded(!showExpanded)}
>
{!showExpanded
? `Show ${inactiveTokens.length} more inactive ${inactiveTokens.length === 1 ? 'token' : 'tokens'}`
: 'Hide expanded search'}
</ButtonLight>
</Row>
)}
</Column>
)}
{inactiveTokens &&
inactiveTokens.length > 0 &&
!(searchToken && !searchTokenIsAdded) &&
(searchQuery.length > 1 || showExpanded) &&
(filteredSortedTokens?.length !== 0 || showExpanded) && (
// button fixed to bottom
<Row align="center" width="100%" justify="center" style={{ position: 'absolute', bottom: '80px', left: 0 }}>
<ButtonLight
width="fit-content"
borderRadius="12px"
padding="8px 12px"
onClick={() => setShowExpanded(!showExpanded)}
>
{!showExpanded
? `Show ${inactiveTokens.length} more inactive ${inactiveTokens.length === 1 ? 'token' : 'tokens'}`
: 'Hide expanded search'}
</ButtonLight>
</Row>
)}
<Footer>
<Row justify="center">
<ButtonText onClick={showManageView} color={theme.blue1} className="list-token-manage-button">

View File

@@ -79,7 +79,7 @@ export default function CurrencySearchModal({
/>
) : modalView === CurrencyModalView.importToken && importToken ? (
<ImportToken
token={importToken}
tokens={[importToken]}
onDismiss={onDismiss}
onBack={() =>
setModalView(prevView && prevView !== CurrencyModalView.importToken ? prevView : CurrencyModalView.search)

View File

@@ -24,6 +24,7 @@ import { useAllLists } from 'state/lists/hooks'
const Wrapper = styled.div`
position: relative;
width: 100%;
overflow: auto;
`
interface ImportProps {

View File

@@ -1,6 +1,6 @@
import React, { CSSProperties } from 'react'
import { Token } from '@uniswap/sdk'
import { RowBetween, RowFixed, AutoRow } from 'components/Row'
import { AutoRow, RowFixed } from 'components/Row'
import { AutoColumn } from 'components/Column'
import CurrencyLogo from 'components/CurrencyLogo'
import { TYPE } from 'theme'
@@ -13,9 +13,15 @@ import styled from 'styled-components'
import { useIsUserAddedToken, useIsTokenActive } from 'hooks/Tokens'
import { CheckCircle } from 'react-feather'
const TokenSection = styled.div`
padding: 8px 20px;
const TokenSection = styled.div<{ dim?: boolean }>`
padding: 4px 20px;
height: 56px;
display: grid;
grid-template-columns: auto minmax(auto, 1fr) auto;
grid-gap: 16px;
align-items: center;
opacity: ${({ dim }) => (dim ? '0.4' : '1')};
`
const CheckIcon = styled(CheckCircle)`
@@ -61,48 +67,42 @@ export default function ImportRow({
return (
<TokenSection style={style}>
<RowBetween>
<AutoRow style={{ opacity: dim ? '0.6' : '1' }}>
<CurrencyLogo currency={token} size={'24px'} />
<AutoColumn gap="4px">
<AutoRow>
<TYPE.body ml="8px" fontWeight={500}>
{token.symbol}
</TYPE.body>
<TYPE.darkGray ml="8px" fontWeight={300}>
<NameOverflow title={token.name}>{token.name}</NameOverflow>
</TYPE.darkGray>
</AutoRow>
{list && list.logoURI && (
<RowFixed style={{ marginLeft: '8px' }}>
<TYPE.small mr="4px" color={theme.text3}>
via {list.name}
</TYPE.small>
<ListLogo logoURI={list.logoURI} size="12px" />
</RowFixed>
)}
</AutoColumn>
<CurrencyLogo currency={token} size={'24px'} style={{ opacity: dim ? '0.6' : '1' }} />
<AutoColumn gap="4px" style={{ opacity: dim ? '0.6' : '1' }}>
<AutoRow>
<TYPE.body fontWeight={500}>{token.symbol}</TYPE.body>
<TYPE.darkGray ml="8px" fontWeight={300}>
<NameOverflow title={token.name}>{token.name}</NameOverflow>
</TYPE.darkGray>
</AutoRow>
{!isActive && !isAdded ? (
<ButtonPrimary
width="fit-content"
padding="6px 12px"
fontWeight={500}
fontSize="14px"
onClick={() => {
setImportToken && setImportToken(token)
showImportView()
}}
>
Import
</ButtonPrimary>
) : (
<RowFixed style={{ minWidth: 'fit-content' }}>
<CheckIcon />
<TYPE.main color={theme.green1}>Active</TYPE.main>
{list && list.logoURI && (
<RowFixed>
<TYPE.small mr="4px" color={theme.text3}>
via {list.name}
</TYPE.small>
<ListLogo logoURI={list.logoURI} size="12px" />
</RowFixed>
)}
</RowBetween>
</AutoColumn>
{!isActive && !isAdded ? (
<ButtonPrimary
width="fit-content"
padding="6px 12px"
fontWeight={500}
fontSize="14px"
onClick={() => {
setImportToken && setImportToken(token)
showImportView()
}}
>
Import
</ButtonPrimary>
) : (
<RowFixed style={{ minWidth: 'fit-content' }}>
<CheckIcon />
<TYPE.main color={theme.green1}>Active</TYPE.main>
</RowFixed>
)}
</TokenSection>
)
}

View File

@@ -22,6 +22,7 @@ import { PaddedColumn, Checkbox } from './styleds'
const Wrapper = styled.div`
position: relative;
width: 100%;
overflow: auto;
`
const WarningWrapper = styled(Card)<{ highWarning: boolean }>`
@@ -39,13 +40,13 @@ const AddressText = styled(TYPE.blue)`
`
interface ImportProps {
token: Token
onBack: () => void
onDismiss: () => void
handleCurrencySelect: (currency: Currency) => void
tokens: Token[]
onBack?: () => void
onDismiss?: () => void
handleCurrencySelect?: (currency: Currency) => void
}
export function ImportToken({ token, onBack, onDismiss, handleCurrencySelect }: ImportProps) {
export function ImportToken({ tokens, onBack, onDismiss, handleCurrencySelect }: ImportProps) {
const theme = useTheme()
const { chainId } = useActiveWeb3React()
@@ -57,86 +58,103 @@ export function ImportToken({ token, onBack, onDismiss, handleCurrencySelect }:
// use for showing import source on inactive tokens
const inactiveTokenList = useCombinedInactiveList()
const list = chainId && inactiveTokenList?.[chainId]?.[token.address]?.list
// higher warning severity if either is not on a list
const fromLists =
(chainId && inactiveTokenList?.[chainId]?.[tokens[0]?.address]?.list) ||
(chainId && inactiveTokenList?.[chainId]?.[tokens[1]?.address]?.list)
return (
<Wrapper>
<PaddedColumn gap="14px" style={{ width: '100%', flex: '1 1' }}>
<RowBetween>
<ArrowLeft style={{ cursor: 'pointer' }} onClick={onBack} />
<TYPE.mediumHeader>Import Token</TYPE.mediumHeader>
<CloseIcon onClick={onDismiss} />
{onBack ? <ArrowLeft style={{ cursor: 'pointer' }} onClick={onBack} /> : <div></div>}
<TYPE.mediumHeader>Import {tokens.length > 1 ? 'Tokens' : 'Token'}</TYPE.mediumHeader>
{onDismiss ? <CloseIcon onClick={onDismiss} /> : <div></div>}
</RowBetween>
</PaddedColumn>
<SectionBreak />
<PaddedColumn gap="md">
<Card backgroundColor={theme.bg2}>
<AutoColumn gap="10px">
<AutoRow align="center">
<CurrencyLogo currency={token} size={'24px'} />
<TYPE.body ml="8px" mr="8px" fontWeight={500}>
{token.symbol}
</TYPE.body>
<TYPE.darkGray fontWeight={300}>{token.name}</TYPE.darkGray>
</AutoRow>
{chainId && (
<ExternalLink href={getEtherscanLink(chainId, token.address, 'address')}>
<AddressText>{token.address}</AddressText>
</ExternalLink>
)}
{list !== undefined ? (
<RowFixed>
{list.logoURI && <ListLogo logoURI={list.logoURI} size="12px" />}
<TYPE.small ml="6px" color={theme.text3}>
via {list.name}
</TYPE.small>
</RowFixed>
) : (
<WarningWrapper borderRadius="4px" padding="4px" highWarning={true}>
<RowFixed>
<AlertTriangle stroke={theme.red1} size="10px" />
<TYPE.body color={theme.red1} ml="4px" fontSize="10px" fontWeight={500}>
Unkown Source
{tokens.map(token => {
const list = chainId && inactiveTokenList?.[chainId]?.[token.address]?.list
return (
<Card backgroundColor={theme.bg2} key={'import' + token.address} className=".token-warning-container">
<AutoColumn gap="10px">
<AutoRow align="center">
<CurrencyLogo currency={token} size={'24px'} />
<TYPE.body ml="8px" mr="8px" fontWeight={500}>
{token.symbol}
</TYPE.body>
</RowFixed>
</WarningWrapper>
)}
</AutoColumn>
</Card>
<Card style={{ backgroundColor: list ? transparentize(0.8, theme.yellow2) : transparentize(0.8, theme.red1) }}>
<TYPE.darkGray fontWeight={300}>{token.name}</TYPE.darkGray>
</AutoRow>
{chainId && (
<ExternalLink href={getEtherscanLink(chainId, token.address, 'address')}>
<AddressText>{token.address}</AddressText>
</ExternalLink>
)}
{list !== undefined ? (
<RowFixed>
{list.logoURI && <ListLogo logoURI={list.logoURI} size="12px" />}
<TYPE.small ml="6px" color={theme.text3}>
via {list.name}
</TYPE.small>
</RowFixed>
) : (
<WarningWrapper borderRadius="4px" padding="4px" highWarning={true}>
<RowFixed>
<AlertTriangle stroke={theme.red1} size="10px" />
<TYPE.body color={theme.red1} ml="4px" fontSize="10px" fontWeight={500}>
Unknown Source
</TYPE.body>
</RowFixed>
</WarningWrapper>
)}
</AutoColumn>
</Card>
)
})}
<Card
style={{ backgroundColor: fromLists ? transparentize(0.8, theme.yellow2) : transparentize(0.8, theme.red1) }}
>
<AutoColumn justify="center" style={{ textAlign: 'center', gap: '16px', marginBottom: '12px' }}>
<AlertTriangle stroke={list ? theme.yellow2 : theme.red1} size={32} />
<TYPE.body fontWeight={600} fontSize={20} color={list ? theme.yellow2 : theme.red1}>
<AlertTriangle stroke={fromLists ? theme.yellow2 : theme.red1} size={32} />
<TYPE.body fontWeight={600} fontSize={20} color={fromLists ? theme.yellow2 : theme.red1}>
Trade at your own risk!
</TYPE.body>
</AutoColumn>
<AutoColumn style={{ textAlign: 'center', gap: '16px', marginBottom: '12px' }}>
<TYPE.body fontWeight={400} color={list ? theme.yellow2 : theme.red1}>
<TYPE.body fontWeight={400} color={fromLists ? theme.yellow2 : theme.red1}>
Anyone can create a token, including creating fake versions of existing tokens that claim to represent
projects.
</TYPE.body>
<TYPE.body fontWeight={600} color={list ? theme.yellow2 : theme.red1}>
<TYPE.body fontWeight={600} color={fromLists ? theme.yellow2 : theme.red1}>
If you purchase this token, you may not be able to sell it back.
</TYPE.body>
</AutoColumn>
<AutoRow justify="center" style={{ cursor: 'pointer' }} onClick={() => setConfirmed(!confirmed)}>
<Checkbox name="confirmed" type="checkbox" checked={confirmed} onChange={() => setConfirmed(!confirmed)} />
<TYPE.body ml="10px" fontSize="16px" color={list ? theme.yellow2 : theme.red1} fontWeight={500}>
<Checkbox
className=".understand-checkbox"
name="confirmed"
type="checkbox"
checked={confirmed}
onChange={() => setConfirmed(!confirmed)}
/>
<TYPE.body ml="10px" fontSize="16px" color={fromLists ? theme.yellow2 : theme.red1} fontWeight={500}>
I understand
</TYPE.body>
</AutoRow>
</Card>
<ButtonPrimary
disabled={!confirmed}
altDisabledStyle={true}
borderRadius="20px"
padding="10px 1rem"
onClick={() => {
addToken(token)
handleCurrencySelect(token)
tokens.map(token => addToken(token))
handleCurrencySelect && handleCurrencySelect(tokens[0])
}}
className=".token-dismiss-button"
>
Import
</ButtonPrimary>

View File

@@ -1,3 +1,4 @@
import { useMemo } from 'react'
import { isAddress } from '../../utils'
import { Token } from '@uniswap/sdk'
@@ -32,13 +33,38 @@ export function filterTokens(tokens: Token[], search: string): Token[] {
const { symbol, name } = token
return (symbol && matchesSearch(symbol)) || (name && matchesSearch(name))
})
// .sort((t0: Token, t1: Token) => {
// if (t0.symbol && matchesSearch(t0.symbol) && t1.symbol && !matchesSearch(t1.symbol)) {
// return -1
// }
// if (t0.symbol && !matchesSearch(t0.symbol) && t1.symbol && matchesSearch(t1.symbol)) {
// return 1
// }
// return 0
// })
}
export function useSortedTokensByQuery(tokens: Token[] | undefined, searchQuery: string): Token[] {
return useMemo(() => {
if (!tokens) {
return []
}
const symbolMatch = searchQuery
.toLowerCase()
.split(/\s+/)
.filter(s => s.length > 0)
if (symbolMatch.length > 1) {
return tokens
}
const exactMatches: Token[] = []
const symbolSubtrings: Token[] = []
const rest: Token[] = []
// sort tokens by exact match -> subtring on symbol match -> rest
tokens.map(token => {
if (token.symbol?.toLowerCase() === symbolMatch[0]) {
return exactMatches.push(token)
} else if (token.symbol?.toLowerCase().startsWith(searchQuery.toLowerCase().trim())) {
return symbolSubtrings.push(token)
} else {
return rest.push(token)
}
})
return [...exactMatches, ...symbolSubtrings, ...rest]
}, [tokens, searchQuery])
}

View File

@@ -1,5 +1,6 @@
import React, { useContext, useRef, useState } from 'react'
import { Settings, X } from 'react-feather'
import ReactGA from 'react-ga'
import { Text } from 'rebass'
import styled, { ThemeContext } from 'styled-components'
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
@@ -236,7 +237,13 @@ export default function SettingsTab() {
<Toggle
id="toggle-disable-multihop-button"
isActive={singleHopOnly}
toggle={() => (singleHopOnly ? setSingleHopOnly(false) : setSingleHopOnly(true))}
toggle={() => {
ReactGA.event({
category: 'Routing',
action: singleHopOnly ? 'disable single hop' : 'enable single hop'
})
setSingleHopOnly(!singleHopOnly)
}}
/>
</RowBetween>
</AutoColumn>

View File

@@ -1,153 +1,22 @@
import { Token } from '@uniswap/sdk'
import { transparentize } from 'polished'
import React, { useCallback, useMemo, useState } from 'react'
import styled from 'styled-components'
import { useActiveWeb3React } from '../../hooks'
import { useAllTokens } from '../../hooks/Tokens'
import { ExternalLink, TYPE } from '../../theme'
import { getEtherscanLink, shortenAddress } from '../../utils'
import CurrencyLogo from '../CurrencyLogo'
import React from 'react'
import Modal from '../Modal'
import { AutoRow, RowBetween } from '../Row'
import { AutoColumn } from '../Column'
import { AlertTriangle } from 'react-feather'
import { ButtonError } from '../Button'
const Wrapper = styled.div<{ error: boolean }>`
background: ${({ theme }) => transparentize(0.6, theme.bg3)};
padding: 0.75rem;
border-radius: 20px;
`
const WarningContainer = styled.div`
max-width: 420px;
width: 100%;
padding: 1rem;
background: rgba(242, 150, 2, 0.05);
border: 1px solid #f3841e;
border-radius: 20px;
overflow: auto;
`
const StyledWarningIcon = styled(AlertTriangle)`
stroke: ${({ theme }) => theme.red2};
`
interface TokenWarningCardProps {
token?: Token
}
function TokenWarningCard({ token }: TokenWarningCardProps) {
const { chainId } = useActiveWeb3React()
const tokenSymbol = token?.symbol?.toLowerCase() ?? ''
const tokenName = token?.name?.toLowerCase() ?? ''
const allTokens = useAllTokens()
const duplicateNameOrSymbol = useMemo(() => {
if (!token || !chainId) return false
return Object.keys(allTokens).some(tokenAddress => {
const userToken = allTokens[tokenAddress]
if (userToken.equals(token)) {
return false
}
return userToken.symbol?.toLowerCase() === tokenSymbol || userToken.name?.toLowerCase() === tokenName
})
}, [token, chainId, allTokens, tokenSymbol, tokenName])
if (!token) return null
return (
<Wrapper error={duplicateNameOrSymbol}>
<AutoRow gap="6px">
<AutoColumn gap="24px">
<CurrencyLogo currency={token} size={'16px'} />
<div> </div>
</AutoColumn>
<AutoColumn gap="10px" justify="flex-start">
<TYPE.main>
{token && token.name && token.symbol && token.name !== token.symbol
? `${token.name} (${token.symbol})`
: token.name || token.symbol}{' '}
</TYPE.main>
{chainId && (
<ExternalLink style={{ fontWeight: 400 }} href={getEtherscanLink(chainId, token.address, 'token')}>
<TYPE.blue title={token.address}>{shortenAddress(token.address)} (View on Etherscan)</TYPE.blue>
</ExternalLink>
)}
</AutoColumn>
</AutoRow>
</Wrapper>
)
}
import { ImportToken } from 'components/SearchModal/ImportToken'
export default function TokenWarningModal({
isOpen,
tokens,
onConfirm
onConfirm,
onDismiss
}: {
isOpen: boolean
tokens: Token[]
onConfirm: () => void
onDismiss: () => void
}) {
const [understandChecked, setUnderstandChecked] = useState(false)
const toggleUnderstand = useCallback(() => setUnderstandChecked(uc => !uc), [])
const handleDismiss = useCallback(() => null, [])
return (
<Modal isOpen={isOpen} onDismiss={handleDismiss} maxHeight={90}>
<WarningContainer className="token-warning-container">
<AutoColumn gap="lg">
<AutoRow gap="6px">
<StyledWarningIcon />
<TYPE.main color={'red2'}>Token imported</TYPE.main>
</AutoRow>
<TYPE.body color={'red2'}>
Anyone can create an ERC20 token on Ethereum with <em>any</em> name, including creating fake versions of
existing tokens and tokens that claim to represent projects that do not have a token.
</TYPE.body>
<TYPE.body color={'red2'}>
This interface can load arbitrary tokens by token addresses. Please take extra caution and do your research
when interacting with arbitrary ERC20 tokens.
</TYPE.body>
<TYPE.body color={'red2'}>
If you purchase an arbitrary token, <strong>you may not be able to sell it back.</strong>
</TYPE.body>
{tokens.map(token => {
return <TokenWarningCard key={token.address} token={token} />
})}
<RowBetween>
<div>
<label style={{ cursor: 'pointer', userSelect: 'none' }}>
<input
type="checkbox"
className="understand-checkbox"
checked={understandChecked}
onChange={toggleUnderstand}
/>{' '}
I understand
</label>
</div>
<ButtonError
disabled={!understandChecked}
error={true}
width={'140px'}
padding="0.5rem 1rem"
className="token-dismiss-button"
style={{
borderRadius: '10px'
}}
onClick={() => {
onConfirm()
}}
>
<TYPE.body color="white">Continue</TYPE.body>
</ButtonError>
</RowBetween>
</AutoColumn>
</WarningContainer>
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={100}>
<ImportToken tokens={tokens} handleCurrencySelect={onConfirm} />
</Modal>
)
}

View File

@@ -1,18 +1,19 @@
import { ChainId } from '@uniswap/sdk'
import { ChainId, Currency } from '@uniswap/sdk'
import React, { useContext } from 'react'
import styled, { ThemeContext } from 'styled-components'
import Modal from '../Modal'
import { ExternalLink } from '../../theme'
import { Text } from 'rebass'
import { CloseIcon, CustomLightSpinner } from '../../theme/components'
import { RowBetween } from '../Row'
import { AlertTriangle, ArrowUpCircle } from 'react-feather'
import { ButtonPrimary } from '../Button'
import { RowBetween, RowFixed } from '../Row'
import { AlertTriangle, ArrowUpCircle, CheckCircle } from 'react-feather'
import { ButtonPrimary, ButtonLight } from '../Button'
import { AutoColumn, ColumnCenter } from '../Column'
import Circle from '../../assets/images/blue-loader.svg'
import MetaMaskLogo from '../../assets/images/metamask.png'
import { getEtherscanLink } from '../../utils'
import { useActiveWeb3React } from '../../hooks'
import useAddTokenToMetamask from 'hooks/useAddTokenToMetamask'
const Wrapper = styled.div`
width: 100%;
@@ -31,6 +32,12 @@ const ConfirmedIcon = styled(ColumnCenter)`
padding: 60px 0;
`
const StyledLogo = styled.img`
height: 16px;
width: 16px;
margin-left: 6px;
`
function ConfirmationPendingContent({ onDismiss, pendingText }: { onDismiss: () => void; pendingText: string }) {
return (
<Wrapper>
@@ -63,14 +70,20 @@ function ConfirmationPendingContent({ onDismiss, pendingText }: { onDismiss: ()
function TransactionSubmittedContent({
onDismiss,
chainId,
hash
hash,
currencyToAdd
}: {
onDismiss: () => void
hash: string | undefined
chainId: ChainId
currencyToAdd?: Currency | undefined
}) {
const theme = useContext(ThemeContext)
const { library } = useActiveWeb3React()
const { addToken, success } = useAddTokenToMetamask(currencyToAdd)
return (
<Wrapper>
<Section>
@@ -92,6 +105,20 @@ function TransactionSubmittedContent({
</Text>
</ExternalLink>
)}
{currencyToAdd && library?.provider?.isMetaMask && (
<ButtonLight mt="12px" padding="6px 12px" width="fit-content" onClick={addToken}>
{!success ? (
<RowFixed>
Add {currencyToAdd.symbol} to Metamask <StyledLogo src={MetaMaskLogo} />
</RowFixed>
) : (
<RowFixed>
Added {currencyToAdd.symbol}{' '}
<CheckCircle size={'16px'} stroke={theme.green1} style={{ marginLeft: '6px' }} />
</RowFixed>
)}
</ButtonLight>
)}
<ButtonPrimary onClick={onDismiss} style={{ margin: '20px 0 0 0' }}>
<Text fontWeight={500} fontSize={20}>
Close
@@ -162,6 +189,7 @@ interface ConfirmationModalProps {
content: () => React.ReactNode
attemptingTxn: boolean
pendingText: string
currencyToAdd?: Currency | undefined
}
export default function TransactionConfirmationModal({
@@ -170,7 +198,8 @@ export default function TransactionConfirmationModal({
attemptingTxn,
hash,
pendingText,
content
content,
currencyToAdd
}: ConfirmationModalProps) {
const { chainId } = useActiveWeb3React()
@@ -182,7 +211,12 @@ export default function TransactionConfirmationModal({
{attemptingTxn ? (
<ConfirmationPendingContent onDismiss={onDismiss} pendingText={pendingText} />
) : hash ? (
<TransactionSubmittedContent chainId={chainId} hash={hash} onDismiss={onDismiss} />
<TransactionSubmittedContent
chainId={chainId}
hash={hash}
onDismiss={onDismiss}
currencyToAdd={currencyToAdd}
/>
) : (
content()
)}

View File

@@ -105,7 +105,7 @@ export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) {
{!showRoute && (
<AutoColumn style={{ padding: '12px 16px 0 16px' }}>
<InfoLink
href={'https://uniswap.info/pair/' + trade.route.pairs[0].liquidityToken.address}
href={'https://info.uniswap.org/pair/' + trade.route.pairs[0].liquidityToken.address}
target="_blank"
>
View pair analytics

View File

@@ -104,6 +104,7 @@ export default function ConfirmSwapModal({
hash={txHash}
content={confirmationContent}
pendingText={pendingText}
currencyToAdd={trade?.outputAmount.currency}
/>
)
}

View File

@@ -64,7 +64,6 @@ export default function UnsupportedCurrencyFooter({
<AutoColumn gap="lg">
<RowBetween>
<TYPE.mediumHeader>Unsupported Assets</TYPE.mediumHeader>
<CloseIcon onClick={() => setShowDetails(false)} />
</RowBetween>
{tokens.map(token => {

View File

@@ -6,10 +6,12 @@ import { PortisConnector } from '@web3-react/portis-connector'
import { FortmaticConnector } from './Fortmatic'
import { NetworkConnector } from './NetworkConnector'
import UNISWAP_LOGO_URL from '../assets/svg/logo.svg'
const NETWORK_URL = process.env.REACT_APP_NETWORK_URL
const FORMATIC_KEY = process.env.REACT_APP_FORTMATIC_KEY
const PORTIS_ID = process.env.REACT_APP_PORTIS_ID
const WALLETCONNECT_BRIDGE_URL = process.env.REACT_APP_WALLETCONNECT_BRIDGE_URL
export const NETWORK_CHAIN_ID: number = parseInt(process.env.REACT_APP_CHAIN_ID ?? '1')
@@ -33,7 +35,7 @@ export const injected = new InjectedConnector({
// mainnet only
export const walletconnect = new WalletConnectConnector({
rpc: { 1: NETWORK_URL },
bridge: 'https://bridge.walletconnect.org',
bridge: WALLETCONNECT_BRIDGE_URL,
qrcode: true,
pollingInterval: 15000
})
@@ -54,6 +56,5 @@ export const portis = new PortisConnector({
export const walletlink = new WalletLinkConnector({
url: NETWORK_URL,
appName: 'Uniswap',
appLogoUrl:
'https://mpng.pngfly.com/20181202/bex/kisspng-emoji-domain-unicorn-pin-badges-sticker-unicorn-tumblr-emoji-unicorn-iphoneemoji-5c046729264a77.5671679315437924251569.jpg'
appLogoUrl: UNISWAP_LOGO_URL
})

View File

@@ -210,5 +210,6 @@ export const BLOCKED_ADDRESSES: string[] = [
'0x7F367cC41522cE07553e823bf3be79A889DEbe1B',
'0xd882cFc20F52f2599D84b8e8D58C7FB62cfE344b',
'0x901bb9583b24D97e995513C6778dc6888AB6870e',
'0xA7e5d5A720f06526557c513402f2e6B5fA20b008'
'0xA7e5d5A720f06526557c513402f2e6B5fA20b008',
'0x8576aCC5C05D6Ce88f4e49bf65BdF0C62F91353C'
]

View File

@@ -1,8 +1,4 @@
// used to mark unsupported tokens, these are hosted lists of unsupported tokens
/**
* @TODO add list from blockchain association
*/
export const UNSUPPORTED_LIST_URLS: string[] = []
const COMPOUND_LIST = 'https://raw.githubusercontent.com/compound-finance/token-list/master/compound.tokenlist.json'
const UMA_LIST = 'https://umaproject.org/uma.tokenlist.json'
@@ -16,6 +12,10 @@ const COINGECKO_LIST = 'https://tokens.coingecko.com/uniswap/all.json'
const CMC_ALL_LIST = 'defi.cmc.eth'
const CMC_STABLECOIN = 'stablecoin.cmc.eth'
const KLEROS_LIST = 't2crtokens.eth'
const GEMINI_LIST = 'https://www.gemini.com/uniswap/manifest.json'
const BA_LIST = 'https://raw.githubusercontent.com/The-Blockchain-Association/sec-notice-list/master/ba-sec-list.json'
export const UNSUPPORTED_LIST_URLS: string[] = [BA_LIST]
// lower index == higher priority for token import
export const DEFAULT_LIST_OF_LISTS: string[] = [
@@ -31,8 +31,9 @@ export const DEFAULT_LIST_OF_LISTS: string[] = [
CMC_ALL_LIST,
CMC_STABLECOIN,
KLEROS_LIST,
GEMINI_LIST,
...UNSUPPORTED_LIST_URLS // need to load unsupported tokens as well
]
// default lists to be 'active' aka searched across
export const DEFAULT_ACTIVE_LIST_URLS: string[] = []
export const DEFAULT_ACTIVE_LIST_URLS: string[] = [GEMINI_LIST]

View File

@@ -1,422 +0,0 @@
{
"name": "Uniswap Default List",
"timestamp": "2021-01-18T18:52:43.076Z",
"version": {
"major": 1,
"minor": 7,
"patch": 0
},
"tags": {},
"logoURI": "ipfs://QmNa8mQkrNKp1WEEeGjFezDmDeodkWRevGFN8JCV7b4Xir",
"keywords": ["uniswap", "default"],
"tokens": [
{
"chainId": 1,
"address": "0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9",
"name": "Aave",
"symbol": "AAVE",
"decimals": 18,
"logoURI": "https://assets.coingecko.com/coins/images/12645/thumb/AAVE.png?1601374110"
},
{
"chainId": 1,
"address": "0xfF20817765cB7f73d4bde2e66e067E58D11095C2",
"name": "Amp",
"symbol": "AMP",
"decimals": 18,
"logoURI": "https://assets.coingecko.com/coins/images/12409/thumb/amp-200x200.png?1599625397"
},
{
"name": "Aragon Network Token",
"address": "0x960b236A07cf122663c4303350609A66A7B288C0",
"symbol": "ANT",
"decimals": 18,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x960b236A07cf122663c4303350609A66A7B288C0/logo.png"
},
{
"name": "Balancer",
"address": "0xba100000625a3754423978a60c9317c58a424e3D",
"symbol": "BAL",
"decimals": 18,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xba100000625a3754423978a60c9317c58a424e3D/logo.png"
},
{
"chainId": 1,
"address": "0xBA11D00c5f74255f56a5E366F4F77f5A186d7f55",
"name": "Band Protocol",
"symbol": "BAND",
"decimals": 18,
"logoURI": "https://assets.coingecko.com/coins/images/9545/thumb/band-protocol.png?1568730326"
},
{
"name": "Bancor Network Token",
"address": "0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C",
"symbol": "BNT",
"decimals": 18,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C/logo.png"
},
{
"name": "Compound",
"address": "0xc00e94Cb662C3520282E6f5717214004A7f26888",
"symbol": "COMP",
"decimals": 18,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xc00e94Cb662C3520282E6f5717214004A7f26888/logo.png"
},
{
"name": "Curve DAO Token",
"address": "0xD533a949740bb3306d119CC777fa900bA034cd52",
"symbol": "CRV",
"decimals": 18,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xD533a949740bb3306d119CC777fa900bA034cd52/logo.png"
},
{
"chainId": 1,
"address": "0x41e5560054824eA6B0732E656E3Ad64E20e94E45",
"name": "Civic",
"symbol": "CVC",
"decimals": 8,
"logoURI": "https://assets.coingecko.com/coins/images/788/thumb/civic.png?1547034556"
},
{
"name": "Dai Stablecoin",
"address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"symbol": "DAI",
"decimals": 18,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x6B175474E89094C44Da98b954EedeAC495271d0F/logo.png"
},
{
"chainId": 1,
"address": "0x0AbdAce70D3790235af448C88547603b945604ea",
"name": "district0x",
"symbol": "DNT",
"decimals": 18,
"logoURI": "https://assets.coingecko.com/coins/images/849/thumb/district0x.png?1547223762"
},
{
"name": "Gnosis Token",
"address": "0x6810e776880C02933D47DB1b9fc05908e5386b96",
"symbol": "GNO",
"decimals": 18,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x6810e776880C02933D47DB1b9fc05908e5386b96/logo.png"
},
{
"chainId": 1,
"address": "0xc944E90C64B2c07662A292be6244BDf05Cda44a7",
"name": "The Graph",
"symbol": "GRT",
"decimals": 18,
"logoURI": "https://assets.coingecko.com/coins/images/13397/thumb/Graph_Token.png?1608145566"
},
{
"chainId": 1,
"address": "0x85Eee30c52B0b379b046Fb0F85F4f3Dc3009aFEC",
"name": "Keep Network",
"symbol": "KEEP",
"decimals": 18,
"logoURI": "https://assets.coingecko.com/coins/images/3373/thumb/IuNzUb5b_400x400.jpg?1589526336"
},
{
"name": "Kyber Network Crystal",
"address": "0xdd974D5C2e2928deA5F71b9825b8b646686BD200",
"symbol": "KNC",
"decimals": 18,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xdd974D5C2e2928deA5F71b9825b8b646686BD200/logo.png"
},
{
"name": "ChainLink Token",
"address": "0x514910771AF9Ca656af840dff83E8264EcF986CA",
"symbol": "LINK",
"decimals": 18,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x514910771AF9Ca656af840dff83E8264EcF986CA/logo.png"
},
{
"name": "Loom Network",
"address": "0xA4e8C3Ec456107eA67d3075bF9e3DF3A75823DB0",
"symbol": "LOOM",
"decimals": 18,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA4e8C3Ec456107eA67d3075bF9e3DF3A75823DB0/logo.png"
},
{
"name": "LoopringCoin V2",
"address": "0xBBbbCA6A901c926F240b89EacB641d8Aec7AEafD",
"symbol": "LRC",
"decimals": 18,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xBBbbCA6A901c926F240b89EacB641d8Aec7AEafD/logo.png"
},
{
"chainId": 1,
"address": "0x0F5D2fB29fb7d3CFeE444a200298f468908cC942",
"name": "Decentraland",
"symbol": "MANA",
"decimals": 18,
"logoURI": "https://assets.coingecko.com/coins/images/878/thumb/decentraland-mana.png?1550108745"
},
{
"name": "Maker",
"address": "0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2",
"symbol": "MKR",
"decimals": 18,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2/logo.png"
},
{
"chainId": 1,
"address": "0xec67005c4E498Ec7f55E092bd1d35cbC47C91892",
"name": "Melon",
"symbol": "MLN",
"decimals": 18,
"logoURI": "https://assets.coingecko.com/coins/images/605/thumb/melon.png?1547034295"
},
{
"name": "Numeraire",
"address": "0x1776e1F26f98b1A5dF9cD347953a26dd3Cb46671",
"symbol": "NMR",
"decimals": 18,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x1776e1F26f98b1A5dF9cD347953a26dd3Cb46671/logo.png"
},
{
"chainId": 1,
"address": "0x4fE83213D56308330EC302a8BD641f1d0113A4Cc",
"name": "NuCypher",
"symbol": "NU",
"decimals": 18,
"logoURI": "https://assets.coingecko.com/coins/images/3318/thumb/photo1198982838879365035.jpg?1547037916"
},
{
"name": "Orchid",
"address": "0x4575f41308EC1483f3d399aa9a2826d74Da13Deb",
"symbol": "OXT",
"decimals": 18,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x4575f41308EC1483f3d399aa9a2826d74Da13Deb/logo.png"
},
{
"name": "Republic Token",
"address": "0x408e41876cCCDC0F92210600ef50372656052a38",
"symbol": "REN",
"decimals": 18,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x408e41876cCCDC0F92210600ef50372656052a38/logo.png"
},
{
"name": "Reputation Augur v1",
"address": "0x1985365e9f78359a9B6AD760e32412f4a445E862",
"symbol": "REP",
"decimals": 18,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x1985365e9f78359a9B6AD760e32412f4a445E862/logo.png"
},
{
"name": "Reputation Augur v2",
"address": "0x221657776846890989a759BA2973e427DfF5C9bB",
"symbol": "REPv2",
"decimals": 18,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x221657776846890989a759BA2973e427DfF5C9bB/logo.png"
},
{
"name": "Synthetix Network Token",
"address": "0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F",
"symbol": "SNX",
"decimals": 18,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F/logo.png"
},
{
"name": "Storj Token",
"address": "0xB64ef51C888972c908CFacf59B47C1AfBC0Ab8aC",
"symbol": "STORJ",
"decimals": 8,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xB64ef51C888972c908CFacf59B47C1AfBC0Ab8aC/logo.png"
},
{
"chainId": 1,
"address": "0x8dAEBADE922dF735c38C80C7eBD708Af50815fAa",
"name": "tBTC",
"symbol": "TBTC",
"decimals": 18,
"logoURI": "https://assets.coingecko.com/coins/images/11224/thumb/tBTC.png?1589620754"
},
{
"name": "UMA Voting Token v1",
"address": "0x04Fa0d235C4abf4BcF4787aF4CF447DE572eF828",
"symbol": "UMA",
"decimals": 18,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x04Fa0d235C4abf4BcF4787aF4CF447DE572eF828/logo.png"
},
{
"name": "Uniswap",
"address": "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984",
"symbol": "UNI",
"decimals": 18,
"chainId": 1,
"logoURI": "ipfs://QmXttGpZrECX5qCyXbBQiqgQNytVGeZW5Anewvh2jc4psg"
},
{
"name": "USDCoin",
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"symbol": "USDC",
"decimals": 6,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"
},
{
"name": "Tether USD",
"address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"symbol": "USDT",
"decimals": 6,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xdAC17F958D2ee523a2206206994597C13D831ec7/logo.png"
},
{
"name": "Wrapped BTC",
"address": "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
"symbol": "WBTC",
"decimals": 8,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599/logo.png"
},
{
"name": "Wrapped Ether",
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"symbol": "WETH",
"decimals": 18,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png"
},
{
"chainId": 1,
"address": "0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e",
"name": "yearn finance",
"symbol": "YFI",
"decimals": 18,
"logoURI": "https://assets.coingecko.com/coins/images/11849/thumb/yfi-192x192.png?1598325330"
},
{
"name": "0x Protocol Token",
"address": "0xE41d2489571d322189246DaFA5ebDe1F4699F498",
"symbol": "ZRX",
"decimals": 18,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xE41d2489571d322189246DaFA5ebDe1F4699F498/logo.png"
},
{
"name": "Dai Stablecoin",
"address": "0xaD6D458402F60fD3Bd25163575031ACDce07538D",
"symbol": "DAI",
"decimals": 18,
"chainId": 3,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xaD6D458402F60fD3Bd25163575031ACDce07538D/logo.png"
},
{
"name": "Uniswap",
"address": "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984",
"symbol": "UNI",
"decimals": 18,
"chainId": 3,
"logoURI": "ipfs://QmXttGpZrECX5qCyXbBQiqgQNytVGeZW5Anewvh2jc4psg"
},
{
"name": "Wrapped Ether",
"address": "0xc778417E063141139Fce010982780140Aa0cD5Ab",
"symbol": "WETH",
"decimals": 18,
"chainId": 3,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xc778417E063141139Fce010982780140Aa0cD5Ab/logo.png"
},
{
"name": "Dai Stablecoin",
"address": "0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735",
"symbol": "DAI",
"decimals": 18,
"chainId": 4,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735/logo.png"
},
{
"name": "Maker",
"address": "0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85",
"symbol": "MKR",
"decimals": 18,
"chainId": 4,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/logo.png"
},
{
"name": "Uniswap",
"address": "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984",
"symbol": "UNI",
"decimals": 18,
"chainId": 4,
"logoURI": "ipfs://QmXttGpZrECX5qCyXbBQiqgQNytVGeZW5Anewvh2jc4psg"
},
{
"name": "Wrapped Ether",
"address": "0xc778417E063141139Fce010982780140Aa0cD5Ab",
"symbol": "WETH",
"decimals": 18,
"chainId": 4,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xc778417E063141139Fce010982780140Aa0cD5Ab/logo.png"
},
{
"name": "Uniswap",
"address": "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984",
"symbol": "UNI",
"decimals": 18,
"chainId": 5,
"logoURI": "ipfs://QmXttGpZrECX5qCyXbBQiqgQNytVGeZW5Anewvh2jc4psg"
},
{
"name": "Wrapped Ether",
"address": "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6",
"symbol": "WETH",
"decimals": 18,
"chainId": 5,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6/logo.png"
},
{
"name": "Dai Stablecoin",
"address": "0x4F96Fe3b7A6Cf9725f59d353F723c1bDb64CA6Aa",
"symbol": "DAI",
"decimals": 18,
"chainId": 42,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x4F96Fe3b7A6Cf9725f59d353F723c1bDb64CA6Aa/logo.png"
},
{
"name": "Maker",
"address": "0xAaF64BFCC32d0F15873a02163e7E500671a4ffcD",
"symbol": "MKR",
"decimals": 18,
"chainId": 42,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xAaF64BFCC32d0F15873a02163e7E500671a4ffcD/logo.png"
},
{
"name": "Uniswap",
"address": "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984",
"symbol": "UNI",
"decimals": 18,
"chainId": 42,
"logoURI": "ipfs://QmXttGpZrECX5qCyXbBQiqgQNytVGeZW5Anewvh2jc4psg"
},
{
"name": "Wrapped Ether",
"address": "0xd0A1E359811322d97991E03f863a0C30C2cF029C",
"symbol": "WETH",
"decimals": 18,
"chainId": 42,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xd0A1E359811322d97991E03f863a0C30C2cF029C/logo.png"
}
]
}

View File

@@ -17,6 +17,14 @@
"decimals": 6,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x4922a015c4407F87432B179bb209e125432E4a2A/logo.png"
},
{
"name": "Grump Cat",
"address": "0x93B2FfF814FCaEFFB01406e80B4Ecd89Ca6A021b",
"symbol": "GRUMPY",
"decimals": 9,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x4922a015c4407F87432B179bb209e125432E4a2A/logo.png"
}
]
}

View File

@@ -150,18 +150,18 @@ export function useTradeExactOut(currencyIn?: Currency, currencyAmountOut?: Curr
}
export function useIsTransactionUnsupported(currencyIn?: Currency, currencyOut?: Currency): boolean {
const unsupportedToken: { [address: string]: Token } = useUnsupportedTokens()
const unsupportedTokens: { [address: string]: Token } = useUnsupportedTokens()
const { chainId } = useActiveWeb3React()
const tokenIn = wrappedCurrency(currencyIn, chainId)
const tokenOut = wrappedCurrency(currencyOut, chainId)
// if unsupported list loaded & either token on list, mark as unsupported
if (unsupportedToken) {
if (tokenIn && Object.keys(unsupportedToken).includes(tokenIn.address)) {
if (unsupportedTokens) {
if (tokenIn && Object.keys(unsupportedTokens).includes(tokenIn.address)) {
return true
}
if (tokenOut && Object.keys(unsupportedToken).includes(tokenOut.address)) {
if (tokenOut && Object.keys(unsupportedTokens).includes(tokenOut.address)) {
return true
}
}

View File

@@ -0,0 +1,43 @@
import { getTokenLogoURL } from './../components/CurrencyLogo/index'
import { wrappedCurrency } from 'utils/wrappedCurrency'
import { Currency, Token } from '@uniswap/sdk'
import { useCallback, useState } from 'react'
import { useActiveWeb3React } from 'hooks'
export default function useAddTokenToMetamask(
currencyToAdd: Currency | undefined
): { addToken: () => void; success: boolean | undefined } {
const { library, chainId } = useActiveWeb3React()
const token: Token | undefined = wrappedCurrency(currencyToAdd, chainId)
const [success, setSuccess] = useState<boolean | undefined>()
const addToken = useCallback(() => {
if (library && library.provider.isMetaMask && library.provider.request && token) {
library.provider
.request({
method: 'wallet_watchAsset',
params: {
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
//@ts-ignore // need this for incorrect ethers provider type
type: 'ERC20',
options: {
address: token.address,
symbol: token.symbol,
decimals: token.decimals,
image: getTokenLogoURL(token.address)
}
}
})
.then(success => {
setSuccess(success)
})
.catch(() => setSuccess(false))
} else {
setSuccess(false)
}
}, [library, token])
return { addToken, success }
}

View File

@@ -11,6 +11,7 @@ import { NetworkContextName } from './constants'
import './i18n'
import App from './pages/App'
import store from './state'
import * as serviceWorkerRegistration from './serviceWorkerRegistration'
import ApplicationUpdater from './state/application/updater'
import ListsUpdater from './state/lists/updater'
import MulticallUpdater from './state/multicall/updater'
@@ -21,14 +22,20 @@ import getLibrary from './utils/getLibrary'
const Web3ProviderNetwork = createWeb3ReactRoot(NetworkContextName)
if ('ethereum' in window) {
;(window.ethereum as any).autoRefreshOnNetworkChange = false
if (!!window.ethereum) {
window.ethereum.autoRefreshOnNetworkChange = false
}
const GOOGLE_ANALYTICS_ID: string | undefined = process.env.REACT_APP_GOOGLE_ANALYTICS_ID
if (typeof GOOGLE_ANALYTICS_ID === 'string') {
ReactGA.initialize(GOOGLE_ANALYTICS_ID)
ReactGA.initialize(GOOGLE_ANALYTICS_ID, {
gaOptions: {
storage: 'none',
storeGac: false
}
})
ReactGA.set({
anonymizeIp: true,
customBrowserType: !isMobile ? 'desktop' : 'web3' in window || 'ethereum' in window ? 'mobileWeb3' : 'mobileRegular'
})
} else {
@@ -75,3 +82,5 @@ ReactDOM.render(
</StrictMode>,
document.getElementById('root')
)
serviceWorkerRegistration.unregister()

View File

@@ -328,6 +328,7 @@ export default function AddLiquidity({
/>
)}
pendingText={pendingText}
currencyToAdd={pair?.liquidityToken}
/>
<AutoColumn gap="20px">
{noLiquidity ||

View File

@@ -24,7 +24,7 @@ import SwapHeader from '../../components/swap/SwapHeader'
import { INITIAL_ALLOWED_SLIPPAGE } from '../../constants'
import { getTradeVersion } from '../../data/V1'
import { useActiveWeb3React } from '../../hooks'
import { useCurrency, useDefaultTokens } from '../../hooks/Tokens'
import { useCurrency, useAllTokens } from '../../hooks/Tokens'
import { ApprovalState, useApproveCallbackFromTrade } from '../../hooks/useApproveCallback'
import useENSAddress from '../../hooks/useENSAddress'
import { useSwapCallback } from '../../hooks/useSwapCallback'
@@ -48,8 +48,9 @@ import Loader from '../../components/Loader'
import { useIsTransactionUnsupported } from 'hooks/Trades'
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
import { isTradeBetter } from 'utils/trades'
import { RouteComponentProps } from 'react-router-dom'
export default function Swap() {
export default function Swap({ history }: RouteComponentProps) {
const loadedUrlParams = useDefaultsFromURLSearch()
// token warning stuff
@@ -66,13 +67,13 @@ export default function Swap() {
setDismissTokenWarning(true)
}, [])
// dismiss warning if all imported tokens are in default list
const defaultTokens = useDefaultTokens()
// dismiss warning if all imported tokens are in active lists
const defaultTokens = useAllTokens()
const importTokensNotInDefault =
urlLoadedTokens &&
urlLoadedTokens.filter((token: Token) => {
return !Boolean(token.address in defaultTokens)
}).length > 0
})
const { account } = useActiveWeb3React()
const theme = useContext(ThemeContext)
@@ -97,6 +98,7 @@ export default function Swap() {
currencies,
inputError: swapInputError
} = useDerivedSwapInfo()
const { wrapType, execute: onWrap, inputError: wrapInputError } = useWrapCallback(
currencies[Field.INPUT],
currencies[Field.OUTPUT],
@@ -142,6 +144,12 @@ export default function Swap() {
[onUserInput]
)
// reset if they close warning without tokens in params
const handleDismissTokenWarning = useCallback(() => {
setDismissTokenWarning(true)
history.push('/swap/')
}, [history])
// modal and loading
const [{ showConfirm, tradeToConfirm, swapErrorMessage, attemptingTxn, txHash }, setSwapState] = useState<{
showConfirm: boolean
@@ -294,14 +302,14 @@ export default function Swap() {
return (
<>
<TokenWarningModal
isOpen={urlLoadedTokens.length > 0 && !dismissTokenWarning && importTokensNotInDefault}
tokens={urlLoadedTokens}
isOpen={importTokensNotInDefault.length > 0 && !dismissTokenWarning}
tokens={importTokensNotInDefault}
onConfirm={handleConfirmTokenWarning}
onDismiss={handleDismissTokenWarning}
/>
<SwapPoolTabs active={'swap'} />
<AppBody>
<SwapHeader />
{/* <Separator /> */}
<Wrapper id="swap-page">
<ConfirmSwapModal
isOpen={showConfirm}

View File

@@ -11,6 +11,7 @@ interface Window {
isMetaMask?: true
on?: (...args: any[]) => void
removeListener?: (...args: any[]) => void
autoRefreshOnNetworkChange?: boolean
}
web3?: {}
}

80
src/service-worker.ts Normal file
View File

@@ -0,0 +1,80 @@
/// <reference lib="webworker" />
/* eslint-disable no-restricted-globals */
// This service worker can be customized!
// See https://developers.google.com/web/tools/workbox/modules
// for the list of available Workbox modules, or add any other
// code you'd like.
// You can also remove this file if you'd prefer not to use a
// service worker, and the Workbox build step will be skipped.
import { clientsClaim } from 'workbox-core'
import { ExpirationPlugin } from 'workbox-expiration'
import { createHandlerBoundToURL, precacheAndRoute } from 'workbox-precaching'
import { registerRoute } from 'workbox-routing'
import { StaleWhileRevalidate } from 'workbox-strategies'
declare const self: ServiceWorkerGlobalScope
clientsClaim()
// Precache all of the assets generated by your build process.
// Their URLs are injected into the manifest variable below.
// This variable must be present somewhere in your service worker file,
// even if you decide not to use precaching. See https://cra.link/PWA
precacheAndRoute(self.__WB_MANIFEST)
// Set up App Shell-style routing, so that all navigation requests
// are fulfilled with your index.html shell. Learn more at
// https://developers.google.com/web/fundamentals/architecture/app-shell
const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$')
registerRoute(
// Return false to exempt requests from being fulfilled by index.html.
({ request, url }: { request: Request; url: URL }) => {
// If this isn't a navigation, skip.
if (request.mode !== 'navigate') {
return false
}
// If this is a URL that starts with /_, skip.
if (url.pathname.startsWith('/_')) {
return false
}
// If this looks like a URL for a resource, because it contains
// a file extension, skip.
if (url.pathname.match(fileExtensionRegexp)) {
return false
}
// Return true to signal that we want to use the handler.
return true
},
createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html')
)
// An example runtime caching route for requests that aren't handled by the
// precache, in this case same-origin .png requests like those from in public/
registerRoute(
// Add in any other file extensions or routing criteria as needed.
({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'),
// Customize this strategy as needed, e.g., by changing to CacheFirst.
new StaleWhileRevalidate({
cacheName: 'images',
plugins: [
// Ensure that once this runtime cache reaches a maximum size the
// least-recently used images are removed.
new ExpirationPlugin({ maxEntries: 50 })
]
})
)
// This allows the web app to trigger skipWaiting via
// registration.waiting.postMessage({type: 'SKIP_WAITING'})
self.addEventListener('message', event => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting()
}
})
// Any other custom service worker logic can go here.

View File

@@ -0,0 +1,139 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://cra.link/PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
)
type Config = {
onSuccess?: (registration: ServiceWorkerRegistration) => void
onUpdate?: (registration: ServiceWorkerRegistration) => void
}
function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing
if (installingWorker == null) {
return
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://cra.link/PWA.'
)
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration)
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.')
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration)
}
}
}
}
}
})
.catch(error => {
console.error('Error during service worker registration:', error)
})
}
function checkValidServiceWorker(swUrl: string, config?: Config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' }
})
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type')
if (response.status === 404 || (contentType != null && contentType.indexOf('javascript') === -1)) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload()
})
})
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config)
}
})
.catch(() => {
console.log('No internet connection found. App is running in offline mode.')
})
}
export function register(config?: Config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href)
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config)
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://cra.link/PWA'
)
})
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config)
}
})
}
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready
.then(registration => {
registration.unregister()
})
.catch(error => {
console.error(error.message)
})
}
}

View File

@@ -29,18 +29,16 @@ function fetchClaim(account: string, chainId: ChainId): Promise<UserClaimData |
return (CLAIM_PROMISES[key] =
CLAIM_PROMISES[key] ??
fetch(`https://gentle-frost-9e74.uniswap.workers.dev/${chainId}/${formatted}`)
.then(res => {
if (res.status === 200) {
return res.json()
} else {
console.debug(`No claim for account ${formatted} on chain ID ${chainId}`)
return null
}
})
.catch(error => {
console.error('Failed to get claim data', error)
}))
fetch('https://merkle-drop-1.uniswap.workers.dev/', {
body: JSON.stringify({ chainId, address: formatted }),
headers: {
'Content-Type': 'application/json',
'Referrer-Policy': 'no-referrer'
},
method: 'POST'
})
.then(res => (res.ok ? res.json() : console.log(`No claim for account ${formatted} on chain ID ${chainId}`)))
.catch(error => console.error('Failed to get claim data', error)))
}
// parse distributorContract blob and detect if user has claim data

View File

@@ -1,5 +1,5 @@
import { UNSUPPORTED_LIST_URLS } from './../../constants/lists'
import DEFAULT_TOKEN_LIST from 'constants/tokenLists/uniswap-default.tokenlist.json'
import DEFAULT_TOKEN_LIST from '@uniswap/default-token-list'
import { ChainId, Token } from '@uniswap/sdk'
import { Tags, TokenInfo, TokenList } from '@uniswap/token-lists'
import { useMemo } from 'react'

View File

@@ -1,4 +1,4 @@
import { DEFAULT_ACTIVE_LIST_URLS } from './../../constants/lists'
import { DEFAULT_ACTIVE_LIST_URLS, UNSUPPORTED_LIST_URLS } from './../../constants/lists'
import { createReducer } from '@reduxjs/toolkit'
import { getVersionUpgrade, VersionUpgrade } from '@uniswap/token-lists'
import { TokenList } from '@uniswap/token-lists/dist/types'
@@ -36,7 +36,7 @@ type Mutable<T> = { -readonly [P in keyof T]: T[P] extends ReadonlyArray<infer U
const initialState: ListsState = {
lastInitializedDefaultListOfLists: DEFAULT_LIST_OF_LISTS,
byUrl: {
...DEFAULT_LIST_OF_LISTS.reduce<Mutable<ListsState['byUrl']>>((memo, listUrl) => {
...DEFAULT_LIST_OF_LISTS.concat(...UNSUPPORTED_LIST_URLS).reduce<Mutable<ListsState['byUrl']>>((memo, listUrl) => {
memo[listUrl] = NEW_LIST_STATE
return memo
}, {})
@@ -74,6 +74,11 @@ export default createReducer(initialState, builder =>
}
}
} else {
// activate if on default active
if (DEFAULT_ACTIVE_LIST_URLS.includes(url)) {
state.activeListUrls?.push(url)
}
state.byUrl[url] = {
...state.byUrl[url],
loadingRequestId: null,

View File

@@ -1,5 +1,4 @@
import { useAllLists } from 'state/lists/hooks'
import { UNSUPPORTED_LIST_URLS } from './../../constants/lists'
import { getVersionUpgrade, minVersionBump, VersionUpgrade } from '@uniswap/token-lists'
import { useCallback, useEffect } from 'react'
import { useDispatch } from 'react-redux'
@@ -11,6 +10,7 @@ import { AppDispatch } from '../index'
import { acceptListUpdate } from './actions'
import { useActiveListUrls } from './hooks'
import { useAllInactiveTokens } from 'hooks/Tokens'
import { UNSUPPORTED_LIST_URLS } from 'constants/lists'
export default function Updater(): null {
const { library } = useActiveWeb3React()
@@ -45,6 +45,16 @@ export default function Updater(): null {
})
}, [dispatch, fetchList, library, lists])
// if any lists from unsupported lists are loaded, check them too (in case new updates since last visit)
useEffect(() => {
Object.keys(UNSUPPORTED_LIST_URLS).forEach(listUrl => {
const list = lists[listUrl]
if (!list || (!list.current && !list.loadingRequestId && !list.error)) {
fetchList(listUrl).catch(error => console.debug('list added fetching error', error))
}
})
}, [dispatch, fetchList, library, lists])
// automatically update lists if versions are minor/patch
useEffect(() => {
Object.keys(lists).forEach(listUrl => {
@@ -67,11 +77,9 @@ export default function Updater(): null {
}
break
// update any active or inactive lists
case VersionUpgrade.MAJOR:
// accept update if list is active or list in background
if (activeListUrls?.includes(listUrl) || UNSUPPORTED_LIST_URLS.includes(listUrl)) {
dispatch(acceptListUpdate(listUrl))
}
dispatch(acceptListUpdate(listUrl))
}
}
})

View File

@@ -1,6 +1,5 @@
import { ChainId, Pair, Token } from '@uniswap/sdk'
import flatMap from 'lodash.flatmap'
import ReactGA from 'react-ga'
import { useCallback, useMemo } from 'react'
import { shallowEqual, useDispatch, useSelector } from 'react-redux'
import { BASES_TO_TRACK_LIQUIDITY_FOR, PINNED_PAIRS } from '../../constants'
@@ -92,10 +91,6 @@ export function useUserSingleHopOnly(): [boolean, (newSingleHopOnly: boolean) =>
const setSingleHopOnly = useCallback(
(newSingleHopOnly: boolean) => {
ReactGA.event({
category: 'Routing',
action: newSingleHopOnly ? 'enable single hop' : 'disable single hop'
})
dispatch(updateUserSingleHopOnly({ userSingleHopOnly: newSingleHopOnly }))
},
[dispatch]
@@ -162,7 +157,7 @@ export function useUserAddedTokens(): Token[] {
return useMemo(() => {
if (!chainId) return []
return Object.values(serializedTokensMap[chainId as ChainId] ?? {}).map(deserializeToken)
return Object.values(serializedTokensMap?.[chainId as ChainId] ?? {}).map(deserializeToken)
}, [serializedTokensMap, chainId])
}

View File

@@ -111,11 +111,17 @@ export default createReducer(initialState, builder =>
state.userSingleHopOnly = action.payload.userSingleHopOnly
})
.addCase(addSerializedToken, (state, { payload: { serializedToken } }) => {
if (!state.tokens) {
state.tokens = {}
}
state.tokens[serializedToken.chainId] = state.tokens[serializedToken.chainId] || {}
state.tokens[serializedToken.chainId][serializedToken.address] = serializedToken
state.timestamp = currentTimestamp()
})
.addCase(removeSerializedToken, (state, { payload: { address, chainId } }) => {
if (!state.tokens) {
state.tokens = {}
}
state.tokens[chainId] = state.tokens[chainId] || {}
delete state.tokens[chainId][address]
state.timestamp = currentTimestamp()

View File

@@ -2691,6 +2691,11 @@
semver "^7.3.2"
tsutils "^3.17.1"
"@uniswap/default-token-list@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@uniswap/default-token-list/-/default-token-list-2.0.0.tgz#095b4c22635e532c817c3ba70e8838d8bd699716"
integrity sha512-P37PqBtUjEB9DIRFfmEsgougkV0555JQKiGPISeN9UFk1UgCQM5sg6+bBaShiyaqEpdtttAaBFI1QESCzPGvXw==
"@uniswap/governance@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@uniswap/governance/-/governance-1.0.2.tgz#7371ab54dea9a5c045275001e2d5325ff2c11a93"
@@ -4795,9 +4800,9 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001035, caniuse-lite@^1.0.30001093, caniuse-lite@^1.0.30001097:
version "1.0.30001105"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001105.tgz#d2cb0b31e5cf2f3ce845033b61c5c01566549abf"
integrity sha512-JupOe6+dGMr7E20siZHIZQwYqrllxotAhiaej96y6x00b/48rPt42o+SzOSCPbrpsDWvRja40Hwrj0g0q6LZJg==
version "1.0.30001183"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001183.tgz"
integrity sha512-7JkwTEE1hlRKETbCFd8HDZeLiQIUcl8rC6JgNjvHCNaxOeNmQ9V4LvQXRUsKIV2CC73qKxljwVhToaA3kLRqTw==
capture-exit@^2.0.0:
version "2.0.0"
@@ -13757,7 +13762,7 @@ serve-static@1.14.1:
parseurl "~1.3.3"
send "0.17.1"
serve@^11.3.0:
serve@^11.3.2:
version "11.3.2"
resolved "https://registry.yarnpkg.com/serve/-/serve-11.3.2.tgz#b905e980616feecd170e51c8f979a7b2374098f5"
integrity sha512-yKWQfI3xbj/f7X1lTBg91fXBP0FqjJ4TEi+ilES5yzH0iKJpN5LjNb1YzIfQg9Rqn4ECUS2SOf2+Kmepogoa5w==
@@ -15650,6 +15655,11 @@ workbox-core@^4.3.1:
resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-4.3.1.tgz#005d2c6a06a171437afd6ca2904a5727ecd73be6"
integrity sha512-I3C9jlLmMKPxAC1t0ExCq+QoAMd0vAAHULEgRZ7kieCdUd919n53WC0AfvokHNwqRhGn+tIIj7vcb5duCjs2Kg==
workbox-core@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-6.1.0.tgz#2671b64f76550e83a4c2202676b67ce372e10881"
integrity sha512-s3KqTJfBreO4xCZpR2LB5p/EknAx8eg0QumKiIgxM4hRO0RtwS2pJvTieNEM23X3RqxRhqweriLD8To19KUvjg==
workbox-expiration@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-4.3.1.tgz#d790433562029e56837f341d7f553c4a78ebe921"
@@ -15657,6 +15667,13 @@ workbox-expiration@^4.3.1:
dependencies:
workbox-core "^4.3.1"
workbox-expiration@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-6.1.0.tgz#cf6bb384e49d0c92b79233c46671d9c6d82478a2"
integrity sha512-jp2xGk+LC4AhCoOxO/bC06GQkq/oVp0ZIf1zXLQh6OD2fWZPkXNjLLSuDnjXoGGPibYrq7gEE/xjAdYGjNWl1A==
dependencies:
workbox-core "^6.1.0"
workbox-google-analytics@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-4.3.1.tgz#9eda0183b103890b5c256e6f4ea15a1f1548519a"
@@ -15681,6 +15698,15 @@ workbox-precaching@^4.3.1:
dependencies:
workbox-core "^4.3.1"
workbox-precaching@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-6.1.0.tgz#9ee3d28f27cd78daa62f5bd6a0d33f5682ac97a7"
integrity sha512-zjye8MVzieBVJ3sS0hFcbKLp7pTHMfJM17YqxCxB0KykXWnxLOpYnStQ9M+bjWJsKJOQvbkPqvq5u9+mtA923g==
dependencies:
workbox-core "^6.1.0"
workbox-routing "^6.1.0"
workbox-strategies "^6.1.0"
workbox-range-requests@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-4.3.1.tgz#f8a470188922145cbf0c09a9a2d5e35645244e74"
@@ -15695,6 +15721,13 @@ workbox-routing@^4.3.1:
dependencies:
workbox-core "^4.3.1"
workbox-routing@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-6.1.0.tgz#f885cb7801e2c9c5678f197656cf27a2b649c1d5"
integrity sha512-FXQ5cwb6Mk90fC0rfQLX0pN+r/N4eBafwkh/QanJUq0e6jMPdDFLrlsikZL/0LcXEx+yAkWLytoiS+d2HOEBOw==
dependencies:
workbox-core "^6.1.0"
workbox-strategies@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-4.3.1.tgz#d2be03c4ef214c115e1ab29c9c759c9fe3e9e646"
@@ -15702,6 +15735,13 @@ workbox-strategies@^4.3.1:
dependencies:
workbox-core "^4.3.1"
workbox-strategies@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-6.1.0.tgz#9ddcee44408d2fb403f22a7989803b5c58560590"
integrity sha512-HvUknzJdZWeV3x7Eq33a7TGAv9/r1TEiQK6kQ1QNzN+IKiqhIjnhKFHmMxb5hK1Gw9/aDSJTLNPDaLPfIJRQFQ==
dependencies:
workbox-core "^6.1.0"
workbox-streams@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-4.3.1.tgz#0b57da70e982572de09c8742dd0cb40a6b7c2cc3"