Compare commits

...

13 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
18 changed files with 91 additions and 43 deletions

3
.env
View File

@@ -1,2 +1,3 @@
REACT_APP_CHAIN_ID="1" 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-api-key: ${{ secrets.PINATA_API_KEY }}
pinata-secret-api-key: ${{ secrets.PINATA_API_SECRET_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 - name: Convert CIDv0 to CIDv1
id: convert_cidv0 id: convert_cidv0
uses: uniswap/convert-cidv0-cidv1@v1.0.0 uses: uniswap/convert-cidv0-cidv1@v1.0.0

View File

@@ -56,7 +56,7 @@ The interface will not work on other networks.
## Contributions ## 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. CI checks will run against all PRs.
## Accessing Uniswap Interface V1 ## Accessing Uniswap Interface V1

View File

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

View File

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

View File

@@ -105,7 +105,7 @@ export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) {
{!showRoute && ( {!showRoute && (
<AutoColumn style={{ padding: '12px 16px 0 16px' }}> <AutoColumn style={{ padding: '12px 16px 0 16px' }}>
<InfoLink <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" target="_blank"
> >
View pair analytics View pair analytics

View File

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

View File

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

View File

@@ -1,8 +1,4 @@
// used to mark unsupported tokens, these are hosted lists of unsupported tokens // 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 COMPOUND_LIST = 'https://raw.githubusercontent.com/compound-finance/token-list/master/compound.tokenlist.json'
const UMA_LIST = 'https://umaproject.org/uma.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 CMC_STABLECOIN = 'stablecoin.cmc.eth'
const KLEROS_LIST = 't2crtokens.eth' const KLEROS_LIST = 't2crtokens.eth'
const GEMINI_LIST = 'https://www.gemini.com/uniswap/manifest.json' 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 // lower index == higher priority for token import
export const DEFAULT_LIST_OF_LISTS: string[] = [ export const DEFAULT_LIST_OF_LISTS: string[] = [

View File

@@ -17,6 +17,14 @@
"decimals": 6, "decimals": 6,
"chainId": 1, "chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x4922a015c4407F87432B179bb209e125432E4a2A/logo.png" "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 { export function useIsTransactionUnsupported(currencyIn?: Currency, currencyOut?: Currency): boolean {
const unsupportedToken: { [address: string]: Token } = useUnsupportedTokens() const unsupportedTokens: { [address: string]: Token } = useUnsupportedTokens()
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
const tokenIn = wrappedCurrency(currencyIn, chainId) const tokenIn = wrappedCurrency(currencyIn, chainId)
const tokenOut = wrappedCurrency(currencyOut, chainId) const tokenOut = wrappedCurrency(currencyOut, chainId)
// if unsupported list loaded & either token on list, mark as unsupported // if unsupported list loaded & either token on list, mark as unsupported
if (unsupportedToken) { if (unsupportedTokens) {
if (tokenIn && Object.keys(unsupportedToken).includes(tokenIn.address)) { if (tokenIn && Object.keys(unsupportedTokens).includes(tokenIn.address)) {
return true return true
} }
if (tokenOut && Object.keys(unsupportedToken).includes(tokenOut.address)) { if (tokenOut && Object.keys(unsupportedTokens).includes(tokenOut.address)) {
return true return true
} }
} }

View File

@@ -28,8 +28,14 @@ if (!!window.ethereum) {
const GOOGLE_ANALYTICS_ID: string | undefined = process.env.REACT_APP_GOOGLE_ANALYTICS_ID const GOOGLE_ANALYTICS_ID: string | undefined = process.env.REACT_APP_GOOGLE_ANALYTICS_ID
if (typeof GOOGLE_ANALYTICS_ID === 'string') { if (typeof GOOGLE_ANALYTICS_ID === 'string') {
ReactGA.initialize(GOOGLE_ANALYTICS_ID) ReactGA.initialize(GOOGLE_ANALYTICS_ID, {
gaOptions: {
storage: 'none',
storeGac: false
}
})
ReactGA.set({ ReactGA.set({
anonymizeIp: true,
customBrowserType: !isMobile ? 'desktop' : 'web3' in window || 'ethereum' in window ? 'mobileWeb3' : 'mobileRegular' customBrowserType: !isMobile ? 'desktop' : 'web3' in window || 'ethereum' in window ? 'mobileWeb3' : 'mobileRegular'
}) })
} else { } else {

View File

@@ -48,8 +48,9 @@ import Loader from '../../components/Loader'
import { useIsTransactionUnsupported } from 'hooks/Trades' import { useIsTransactionUnsupported } from 'hooks/Trades'
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter' import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
import { isTradeBetter } from 'utils/trades' import { isTradeBetter } from 'utils/trades'
import { RouteComponentProps } from 'react-router-dom'
export default function Swap() { export default function Swap({ history }: RouteComponentProps) {
const loadedUrlParams = useDefaultsFromURLSearch() const loadedUrlParams = useDefaultsFromURLSearch()
// token warning stuff // token warning stuff
@@ -97,6 +98,7 @@ export default function Swap() {
currencies, currencies,
inputError: swapInputError inputError: swapInputError
} = useDerivedSwapInfo() } = useDerivedSwapInfo()
const { wrapType, execute: onWrap, inputError: wrapInputError } = useWrapCallback( const { wrapType, execute: onWrap, inputError: wrapInputError } = useWrapCallback(
currencies[Field.INPUT], currencies[Field.INPUT],
currencies[Field.OUTPUT], currencies[Field.OUTPUT],
@@ -142,6 +144,12 @@ export default function Swap() {
[onUserInput] [onUserInput]
) )
// reset if they close warning without tokens in params
const handleDismissTokenWarning = useCallback(() => {
setDismissTokenWarning(true)
history.push('/swap/')
}, [history])
// modal and loading // modal and loading
const [{ showConfirm, tradeToConfirm, swapErrorMessage, attemptingTxn, txHash }, setSwapState] = useState<{ const [{ showConfirm, tradeToConfirm, swapErrorMessage, attemptingTxn, txHash }, setSwapState] = useState<{
showConfirm: boolean showConfirm: boolean
@@ -297,6 +305,7 @@ export default function Swap() {
isOpen={importTokensNotInDefault.length > 0 && !dismissTokenWarning} isOpen={importTokensNotInDefault.length > 0 && !dismissTokenWarning}
tokens={importTokensNotInDefault} tokens={importTokensNotInDefault}
onConfirm={handleConfirmTokenWarning} onConfirm={handleConfirmTokenWarning}
onDismiss={handleDismissTokenWarning}
/> />
<SwapPoolTabs active={'swap'} /> <SwapPoolTabs active={'swap'} />
<AppBody> <AppBody>

View File

@@ -29,18 +29,16 @@ function fetchClaim(account: string, chainId: ChainId): Promise<UserClaimData |
return (CLAIM_PROMISES[key] = return (CLAIM_PROMISES[key] =
CLAIM_PROMISES[key] ?? CLAIM_PROMISES[key] ??
fetch(`https://gentle-frost-9e74.uniswap.workers.dev/${chainId}/${formatted}`) fetch('https://merkle-drop-1.uniswap.workers.dev/', {
.then(res => { body: JSON.stringify({ chainId, address: formatted }),
if (res.status === 200) { headers: {
return res.json() 'Content-Type': 'application/json',
} else { 'Referrer-Policy': 'no-referrer'
console.debug(`No claim for account ${formatted} on chain ID ${chainId}`) },
return null method: 'POST'
} })
}) .then(res => (res.ok ? res.json() : console.log(`No claim for account ${formatted} on chain ID ${chainId}`)))
.catch(error => { .catch(error => console.error('Failed to get claim data', error)))
console.error('Failed to get claim data', error)
}))
} }
// parse distributorContract blob and detect if user has claim data // 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 { createReducer } from '@reduxjs/toolkit'
import { getVersionUpgrade, VersionUpgrade } from '@uniswap/token-lists' import { getVersionUpgrade, VersionUpgrade } from '@uniswap/token-lists'
import { TokenList } from '@uniswap/token-lists/dist/types' 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 = { const initialState: ListsState = {
lastInitializedDefaultListOfLists: DEFAULT_LIST_OF_LISTS, lastInitializedDefaultListOfLists: DEFAULT_LIST_OF_LISTS,
byUrl: { 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 memo[listUrl] = NEW_LIST_STATE
return memo return memo
}, {}) }, {})

View File

@@ -10,6 +10,7 @@ import { AppDispatch } from '../index'
import { acceptListUpdate } from './actions' import { acceptListUpdate } from './actions'
import { useActiveListUrls } from './hooks' import { useActiveListUrls } from './hooks'
import { useAllInactiveTokens } from 'hooks/Tokens' import { useAllInactiveTokens } from 'hooks/Tokens'
import { UNSUPPORTED_LIST_URLS } from 'constants/lists'
export default function Updater(): null { export default function Updater(): null {
const { library } = useActiveWeb3React() const { library } = useActiveWeb3React()
@@ -44,6 +45,16 @@ export default function Updater(): null {
}) })
}, [dispatch, fetchList, library, lists]) }, [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 // automatically update lists if versions are minor/patch
useEffect(() => { useEffect(() => {
Object.keys(lists).forEach(listUrl => { Object.keys(lists).forEach(listUrl => {

View File

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

View File

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