Compare commits

...

24 Commits

Author SHA1 Message Date
dependabot[bot]
e4dd4f9283 chore(deps): bump ini from 1.3.5 to 1.3.8 (#1277)
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.8)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-19 10:55:13 -05:00
Moody Salem
7798443919 improvement(walletconnect): upgrade the walletconnect connector to 6.1.9 2021-04-19 09:52:41 -05:00
Noah Zinsmeister
392d78b9da add to ADDITIONAL_BASES 2021-04-16 13:54:31 -04:00
Noah Zinsmeister
231289732c add additional bases (#1363)
* add routing token

* add logic for additional bases

* update address
2021-04-16 13:47:38 -04:00
Brandon Curtis
140ff7a674 update BASES_TO_CHECK_TRADES_AGAINST (#1333) 2021-04-12 11:32:45 -05:00
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
25 changed files with 1074 additions and 248 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

@@ -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

@@ -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

@@ -82,18 +82,21 @@
"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"
},
"resolutions": {
"@walletconnect/web3-provider": "1.1.1-alpha.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"
},
"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",

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"
}

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

@@ -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,21 +1,21 @@
import { Token } from '@uniswap/sdk'
import React, { useCallback } from 'react'
import React from 'react'
import Modal from '../Modal'
import { ImportToken } from 'components/SearchModal/ImportToken'
export default function TokenWarningModal({
isOpen,
tokens,
onConfirm
onConfirm,
onDismiss
}: {
isOpen: boolean
tokens: Token[]
onConfirm: () => void
onDismiss: () => void
}) {
const handleDismiss = useCallback(() => null, [])
return (
<Modal isOpen={isOpen} onDismiss={handleDismiss} maxHeight={90}>
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={100}>
<ImportToken tokens={tokens} handleCurrencySelect={onConfirm} />
</Modal>
)

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

@@ -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

@@ -14,13 +14,16 @@ type ChainTokenList = {
readonly [chainId in ChainId]: Token[]
}
export const AMPL = new Token(ChainId.MAINNET, '0xD46bA6D942050d489DBd938a2C909A5d5039A161', 9, 'AMPL', 'Ampleforth')
export const DAI = new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin')
export const USDC = new Token(ChainId.MAINNET, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC', 'USD//C')
export const USDT = new Token(ChainId.MAINNET, '0xdAC17F958D2ee523a2206206994597C13D831ec7', 6, 'USDT', 'Tether USD')
export const COMP = new Token(ChainId.MAINNET, '0xc00e94Cb662C3520282E6f5717214004A7f26888', 18, 'COMP', 'Compound')
export const MKR = new Token(ChainId.MAINNET, '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2', 18, 'MKR', 'Maker')
export const AMPL = new Token(ChainId.MAINNET, '0xD46bA6D942050d489DBd938a2C909A5d5039A161', 9, 'AMPL', 'Ampleforth')
export const WBTC = new Token(ChainId.MAINNET, '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', 8, 'WBTC', 'Wrapped BTC')
export const FEI = new Token(ChainId.MAINNET, '0x956F47F50A910163D8BF957Cf5846D573E7f87CA', 18, 'FEI', 'Fei USD')
export const TRIBE = new Token(ChainId.MAINNET, '0xc7283b66Eb1EB5FB86327f08e1B5816b0720212B', 18, 'TRIBE', 'Tribe')
export const FRAX = new Token(ChainId.MAINNET, '0x853d955aCEf822Db058eb8505911ED77F175b99e', 18, 'FRAX', 'Frax')
export const FXS = new Token(ChainId.MAINNET, '0x3432B6A60D23Ca0dFCa7761B7ab56459D9C964D0', 18, 'FXS', 'Frax Share')
export const renBTC = new Token(ChainId.MAINNET, '0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D', 8, 'renBTC', 'renBTC')
// Block time here is slightly higher (~1s) than average in order to avoid ongoing proposals past the displayed time
export const AVERAGE_BLOCK_TIME_IN_SECS = 13
@@ -62,7 +65,20 @@ const WETH_ONLY: ChainTokenList = {
// used to construct intermediary pairs for trading
export const BASES_TO_CHECK_TRADES_AGAINST: ChainTokenList = {
...WETH_ONLY,
[ChainId.MAINNET]: [...WETH_ONLY[ChainId.MAINNET], DAI, USDC, USDT, COMP, MKR, WBTC]
[ChainId.MAINNET]: [...WETH_ONLY[ChainId.MAINNET], DAI, USDC, USDT, WBTC]
}
export const ADDITIONAL_BASES: { [chainId in ChainId]?: { [tokenAddress: string]: Token[] } } = {
[ChainId.MAINNET]: {
'0xA948E86885e12Fb09AfEF8C52142EBDbDf73cD18': [new Token(ChainId.MAINNET, UNI_ADDRESS, 18, 'UNI', 'Uniswap')],
'0x561a4717537ff4AF5c687328c0f7E90a319705C0': [new Token(ChainId.MAINNET, UNI_ADDRESS, 18, 'UNI', 'Uniswap')],
[FEI.address]: [TRIBE],
[TRIBE.address]: [FEI],
[FRAX.address]: [FXS],
[FXS.address]: [FRAX],
[WBTC.address]: [renBTC],
[renBTC.address]: [WBTC]
}
}
/**
@@ -210,5 +226,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'
@@ -17,6 +13,9 @@ 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[] = [

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

@@ -3,7 +3,12 @@ import { Currency, CurrencyAmount, Pair, Token, Trade } from '@uniswap/sdk'
import flatMap from 'lodash.flatmap'
import { useMemo } from 'react'
import { BASES_TO_CHECK_TRADES_AGAINST, CUSTOM_BASES, BETTER_TRADE_LESS_HOPS_THRESHOLD } from '../constants'
import {
BASES_TO_CHECK_TRADES_AGAINST,
CUSTOM_BASES,
BETTER_TRADE_LESS_HOPS_THRESHOLD,
ADDITIONAL_BASES
} from '../constants'
import { PairState, usePairs } from '../data/Reserves'
import { wrappedCurrency } from '../utils/wrappedCurrency'
@@ -14,17 +19,22 @@ import { useUserSingleHopOnly } from 'state/user/hooks'
function useAllCommonPairs(currencyA?: Currency, currencyB?: Currency): Pair[] {
const { chainId } = useActiveWeb3React()
const bases: Token[] = chainId ? BASES_TO_CHECK_TRADES_AGAINST[chainId] : []
const [tokenA, tokenB] = chainId
? [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)]
: [undefined, undefined]
const bases: Token[] = useMemo(() => {
if (!chainId) return []
const common = BASES_TO_CHECK_TRADES_AGAINST[chainId] ?? []
const additionalA = tokenA ? ADDITIONAL_BASES[chainId]?.[tokenA.address] ?? [] : []
const additionalB = tokenB ? ADDITIONAL_BASES[chainId]?.[tokenB.address] ?? [] : []
return [...common, ...additionalA, ...additionalB]
}, [chainId, tokenA, tokenB])
const basePairs: [Token, Token][] = useMemo(
() =>
flatMap(bases, (base): [Token, Token][] => bases.map(otherBase => [base, otherBase])).filter(
([t0, t1]) => t0.address !== t1.address
),
() => flatMap(bases, (base): [Token, Token][] => bases.map(otherBase => [base, otherBase])),
[bases]
)
@@ -46,10 +56,9 @@ function useAllCommonPairs(currencyA?: Currency, currencyB?: Currency): Pair[] {
.filter(([tokenA, tokenB]) => {
if (!chainId) return true
const customBases = CUSTOM_BASES[chainId]
if (!customBases) return true
const customBasesA: Token[] | undefined = customBases[tokenA.address]
const customBasesB: Token[] | undefined = customBases[tokenB.address]
const customBasesA: Token[] | undefined = customBases?.[tokenA.address]
const customBasesB: Token[] | undefined = customBases?.[tokenB.address]
if (!customBasesA && !customBasesB) return true
@@ -150,18 +159,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

@@ -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'
@@ -27,8 +28,14 @@ if (!!window.ethereum) {
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

@@ -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
@@ -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
@@ -297,6 +305,7 @@ export default function Swap() {
isOpen={importTokensNotInDefault.length > 0 && !dismissTokenWarning}
tokens={importTokensNotInDefault}
onConfirm={handleConfirmTokenWarning}
onDismiss={handleDismissTokenWarning}
/>
<SwapPoolTabs active={'swap'} />
<AppBody>

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,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
}, {})

View File

@@ -10,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()
@@ -44,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 => {

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()

877
yarn.lock

File diff suppressed because it is too large Load Diff