Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe35ca9db8 | ||
|
|
7801695180 | ||
|
|
5a9034fe95 | ||
|
|
6d5625a1f8 | ||
|
|
d425ff64b4 | ||
|
|
2f47fdf71d | ||
|
|
9f8719f2a5 | ||
|
|
77b640c41b | ||
|
|
d2f98bc9b4 | ||
|
|
e4dd4f9283 | ||
|
|
7798443919 | ||
|
|
392d78b9da | ||
|
|
231289732c | ||
|
|
140ff7a674 | ||
|
|
3fbc4e34f4 | ||
|
|
b964953daf | ||
|
|
649fd9c845 | ||
|
|
6347e63a15 | ||
|
|
bdcb9a8a0a | ||
|
|
8d90bb7a39 | ||
|
|
d70b456855 | ||
|
|
fbb797fa54 | ||
|
|
8ace518311 | ||
|
|
67c776c995 | ||
|
|
719754c46c | ||
|
|
9170af888e | ||
|
|
b258f557d1 | ||
|
|
9d8c7f8e12 | ||
|
|
9c44e61e23 | ||
|
|
71db11b6ac | ||
|
|
db3328c8d9 | ||
|
|
34dfb41a1e | ||
|
|
e77fcd21dc |
3
.env
3
.env
@@ -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"
|
||||
38
.github/workflows/integration-tests.yaml
vendored
Normal file
38
.github/workflows/integration-tests.yaml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: Integration Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
integration-tests:
|
||||
name: Cypress
|
||||
runs-on: ubuntu-16.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- run: yarn cypress install
|
||||
- run: yarn build
|
||||
env:
|
||||
CI: false
|
||||
REACT_APP_NETWORK_URL: "https://mainnet.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847"
|
||||
|
||||
- run: yarn integration-test
|
||||
env:
|
||||
CYPRESS_INTEGRATION_TEST_PRIVATE_KEY: ${{ secrets.CYPRESS_INTEGRATION_TEST_PRIVATE_KEY }}
|
||||
|
||||
|
||||
21
.github/workflows/lint.yml
vendored
21
.github/workflows/lint.yml
vendored
@@ -14,33 +14,20 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 12
|
||||
always-auth: true
|
||||
node-version: 14
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
- name: Set output of cache
|
||||
id: yarn-cache
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- name: Node dependency cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.yarn-cache.outputs.dir }}
|
||||
key: yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
yarn-
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Run linters
|
||||
uses: wearerequired/lint-action@77d70b9a07ecb93bc98dc46dc27d96c4f004d035
|
||||
uses: wearerequired/lint-action@b98b0918aa71490373d2eca9e8e39a9bc1cc2517
|
||||
with:
|
||||
github_token: ${{ secrets.github_token }}
|
||||
eslint: true
|
||||
|
||||
18
.github/workflows/release.yaml
vendored
18
.github/workflows/release.yaml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
changelog: ${{ steps.github_tag_action.outputs.changelog }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Bump version and push tag
|
||||
id: github_tag_action
|
||||
@@ -31,12 +31,12 @@ jobs:
|
||||
if: ${{ needs.bump_version.outputs.new_tag != null }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/setup-node@v1
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '12'
|
||||
always-auth: true
|
||||
node-version: 14
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
- name: Install dependencies
|
||||
@@ -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
|
||||
|
||||
65
.github/workflows/tests.yaml
vendored
65
.github/workflows/tests.yaml
vendored
@@ -1,65 +0,0 @@
|
||||
name: Tests
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
integration-tests:
|
||||
name: Integration tests
|
||||
runs-on: ubuntu-16.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '12'
|
||||
always-auth: true
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- uses: actions/cache@v1
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn cypress install
|
||||
- run: yarn build
|
||||
env:
|
||||
CI: false
|
||||
REACT_APP_NETWORK_URL: "https://mainnet.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847"
|
||||
- run: yarn integration-test
|
||||
|
||||
unit-tests:
|
||||
name: Unit tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '12'
|
||||
always-auth: true
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- uses: actions/cache@v1
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn test
|
||||
|
||||
28
.github/workflows/unit-tests.yaml
vendored
Normal file
28
.github/workflows/unit-tests.yaml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: Unit Tests
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
unit-tests:
|
||||
name: Unit tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Run unit tests
|
||||
run: yarn test
|
||||
@@ -1,8 +1,9 @@
|
||||
# Uniswap Interface
|
||||
|
||||
[](https://github.com/Uniswap/uniswap-interface/actions?query=workflow%3ALint)
|
||||
[](https://github.com/Uniswap/uniswap-interface/actions?query=workflow%3ATests)
|
||||
[](https://prettier.io/)
|
||||
[](https://github.com/Uniswap/uniswap-interface/actions/workflows/unit-tests.yaml)
|
||||
[](https://github.com/Uniswap/uniswap-interface/actions/workflows/integration-tests.yaml)
|
||||
[](https://github.com/Uniswap/uniswap-interface/actions/workflows/lint.yml)
|
||||
[](https://github.com/Uniswap/uniswap-interface/actions/workflows/release.yaml)
|
||||
|
||||
An open source interface for Uniswap -- a protocol for decentralized exchange of Ethereum tokens.
|
||||
|
||||
@@ -56,7 +57,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
|
||||
|
||||
@@ -8,13 +8,15 @@ import { JsonRpcProvider } from '@ethersproject/providers'
|
||||
import { Wallet } from '@ethersproject/wallet'
|
||||
import { _Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge'
|
||||
|
||||
// never send real ether to this, obviously
|
||||
const PRIVATE_KEY_TEST_NEVER_USE = '0xad20c82497421e9784f18460ad2fe84f73569068e98e270b3e63743268af5763'
|
||||
const TEST_PRIVATE_KEY = Cypress.env('INTEGRATION_TEST_PRIVATE_KEY')
|
||||
|
||||
// address of the above key
|
||||
export const TEST_ADDRESS_NEVER_USE = '0x0fF2D1eFd7A57B7562b2bf27F3f37899dB27F4a5'
|
||||
export const TEST_ADDRESS_NEVER_USE = new Wallet(TEST_PRIVATE_KEY).address
|
||||
|
||||
export const TEST_ADDRESS_NEVER_USE_SHORTENED = '0x0fF2...F4a5'
|
||||
export const TEST_ADDRESS_NEVER_USE_SHORTENED = `${TEST_ADDRESS_NEVER_USE.substr(
|
||||
0,
|
||||
6
|
||||
)}...${TEST_ADDRESS_NEVER_USE.substr(-4, 4)}`
|
||||
|
||||
class CustomizedBridge extends _Eip1193Bridge {
|
||||
async sendAsync(...args) {
|
||||
@@ -75,7 +77,7 @@ Cypress.Commands.overwrite('visit', (original, url, options) => {
|
||||
options && options.onBeforeLoad && options.onBeforeLoad(win)
|
||||
win.localStorage.clear()
|
||||
const provider = new JsonRpcProvider('https://rinkeby.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847', 4)
|
||||
const signer = new Wallet(PRIVATE_KEY_TEST_NEVER_USE, provider)
|
||||
const signer = new Wallet(TEST_PRIVATE_KEY, provider)
|
||||
win.ethereum = new CustomizedBridge(signer, provider)
|
||||
}
|
||||
})
|
||||
|
||||
13
package.json
13
package.json
@@ -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",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.6 KiB |
@@ -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"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 14 KiB |
@@ -9,27 +9,29 @@ import { useActiveWeb3React } from '../../hooks'
|
||||
const StyledPolling = styled.div`
|
||||
position: fixed;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: 1rem;
|
||||
color: white;
|
||||
transition: opacity 0.25s ease;
|
||||
color: ${({ theme }) => theme.green1};
|
||||
:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
display: none;
|
||||
`}
|
||||
`
|
||||
const StyledPollingNumber = styled(TYPE.small)<{ breathe: boolean; hovering: boolean }>`
|
||||
transition: opacity 0.25s ease;
|
||||
opacity: ${({ breathe, hovering }) => (hovering ? 0.7 : breathe ? 1 : 0.2)};
|
||||
:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
`
|
||||
const StyledPollingDot = styled.div`
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
min-height: 8px;
|
||||
min-width: 8px;
|
||||
margin-left: 0.5rem;
|
||||
margin-top: 3px;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
background-color: ${({ theme }) => theme.green1};
|
||||
@@ -67,16 +69,21 @@ export default function Polling() {
|
||||
|
||||
const blockNumber = useBlockNumber()
|
||||
|
||||
const [isMounted, setIsMounted] = useState(true)
|
||||
const [isMounting, setIsMounting] = useState(false)
|
||||
const [isHover, setIsHover] = useState(false)
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
const timer1 = setTimeout(() => setIsMounted(true), 1000)
|
||||
if (!blockNumber) {
|
||||
return
|
||||
}
|
||||
|
||||
setIsMounting(true)
|
||||
const mountingTimer = setTimeout(() => setIsMounting(false), 1000)
|
||||
|
||||
// this will clear Timeout when component unmount like in willComponentUnmount
|
||||
return () => {
|
||||
setIsMounted(false)
|
||||
clearTimeout(timer1)
|
||||
clearTimeout(mountingTimer)
|
||||
}
|
||||
},
|
||||
[blockNumber] //useEffect will run only one time
|
||||
@@ -85,9 +92,11 @@ export default function Polling() {
|
||||
|
||||
return (
|
||||
<ExternalLink href={chainId && blockNumber ? getEtherscanLink(chainId, blockNumber.toString(), 'block') : ''}>
|
||||
<StyledPolling>
|
||||
<TYPE.small style={{ opacity: isMounted ? '0.2' : '0.6' }}>{blockNumber}</TYPE.small>
|
||||
<StyledPollingDot>{!isMounted && <Spinner />}</StyledPollingDot>
|
||||
<StyledPolling onMouseEnter={() => setIsHover(true)} onMouseLeave={() => setIsHover(false)}>
|
||||
<StyledPollingNumber breathe={isMounting} hovering={isHover}>
|
||||
{blockNumber}
|
||||
</StyledPollingNumber>
|
||||
<StyledPollingDot>{isMounting && <Spinner />}</StyledPollingDot>
|
||||
</StyledPolling>
|
||||
</ExternalLink>
|
||||
)
|
||||
|
||||
@@ -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
|
||||
? {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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 ↗
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
@@ -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'
|
||||
]
|
||||
|
||||
@@ -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[] = [
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -3,27 +3,33 @@ import { AutoColumn } from '../../components/Column'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { RouteComponentProps } from 'react-router-dom'
|
||||
import { TYPE, StyledInternalLink, ExternalLink } from '../../theme'
|
||||
import { RowFixed, RowBetween } from '../../components/Row'
|
||||
import { ExternalLink, StyledInternalLink, TYPE } from '../../theme'
|
||||
import { RowBetween, RowFixed } from '../../components/Row'
|
||||
import { CardSection, DataCard } from '../../components/earn/styled'
|
||||
import { ArrowLeft } from 'react-feather'
|
||||
import { ButtonPrimary } from '../../components/Button'
|
||||
import { ProposalStatus } from './styled'
|
||||
import { useProposalData, useUserVotesAsOfBlock, ProposalData, useUserDelegatee } from '../../state/governance/hooks'
|
||||
import {
|
||||
ProposalData,
|
||||
ProposalState,
|
||||
useProposalData,
|
||||
useUserDelegatee,
|
||||
useUserVotesAsOfBlock
|
||||
} from '../../state/governance/hooks'
|
||||
import { DateTime } from 'luxon'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import VoteModal from '../../components/vote/VoteModal'
|
||||
import { TokenAmount, JSBI } from '@uniswap/sdk'
|
||||
import { JSBI, TokenAmount } from '@uniswap/sdk'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { AVERAGE_BLOCK_TIME_IN_SECS, COMMON_CONTRACT_NAMES, UNI, ZERO_ADDRESS } from '../../constants'
|
||||
import { isAddress, getEtherscanLink } from '../../utils'
|
||||
import { getEtherscanLink, isAddress } from '../../utils'
|
||||
import { ApplicationModal } from '../../state/application/actions'
|
||||
import { useModalOpen, useToggleDelegateModal, useToggleVoteModal, useBlockNumber } from '../../state/application/hooks'
|
||||
import { useBlockNumber, useModalOpen, useToggleDelegateModal, useToggleVoteModal } from '../../state/application/hooks'
|
||||
import DelegateModal from '../../components/vote/DelegateModal'
|
||||
import { GreyCard } from '../../components/Card'
|
||||
import { useTokenBalance } from '../../state/wallet/hooks'
|
||||
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
|
||||
import { BigNumber } from 'ethers'
|
||||
import { GreyCard } from '../../components/Card'
|
||||
|
||||
const PageWrapper = styled(AutoColumn)`
|
||||
width: 100%;
|
||||
@@ -152,7 +158,7 @@ export default function VotePage({
|
||||
availableVotes &&
|
||||
JSBI.greaterThan(availableVotes.raw, JSBI.BigInt(0)) &&
|
||||
proposalData &&
|
||||
proposalData.status === 'active'
|
||||
proposalData.status === ProposalState.Active
|
||||
|
||||
const uniBalance: TokenAmount | undefined = useTokenBalance(account ?? undefined, chainId ? UNI[chainId] : undefined)
|
||||
const userDelegatee: string | undefined = useUserDelegatee()
|
||||
@@ -181,7 +187,9 @@ export default function VotePage({
|
||||
<ArrowWrapper to="/vote">
|
||||
<ArrowLeft size={20} /> All Proposals
|
||||
</ArrowWrapper>
|
||||
{proposalData && <ProposalStatus status={proposalData?.status ?? ''}>{proposalData?.status}</ProposalStatus>}
|
||||
{proposalData && (
|
||||
<ProposalStatus status={proposalData?.status}>{ProposalState[proposalData?.status]}</ProposalStatus>
|
||||
)}
|
||||
</RowBetween>
|
||||
<AutoColumn gap="10px" style={{ width: '100%' }}>
|
||||
<TYPE.largeHeader style={{ marginBottom: '.5rem' }}>{proposalData?.title}</TYPE.largeHeader>
|
||||
@@ -194,7 +202,7 @@ export default function VotePage({
|
||||
: ''}
|
||||
</TYPE.main>
|
||||
</RowBetween>
|
||||
{proposalData && proposalData.status === 'active' && !showVotingButtons && (
|
||||
{proposalData && proposalData.status === ProposalState.Active && !showVotingButtons && (
|
||||
<GreyCard>
|
||||
<TYPE.black>
|
||||
Only UNI votes that were self delegated or delegated to another address before block{' '}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import { AutoColumn } from '../../components/Column'
|
||||
import styled from 'styled-components'
|
||||
import { TYPE, ExternalLink } from '../../theme'
|
||||
import { ExternalLink, TYPE } from '../../theme'
|
||||
import { RowBetween, RowFixed } from '../../components/Row'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { ProposalStatus } from './styled'
|
||||
@@ -9,14 +9,20 @@ import { ButtonPrimary } from '../../components/Button'
|
||||
|
||||
import { Button } from 'rebass/styled-components'
|
||||
import { darken } from 'polished'
|
||||
import { CardSection, DataCard, CardNoise, CardBGImage } from '../../components/earn/styled'
|
||||
import { useAllProposalData, ProposalData, useUserVotes, useUserDelegatee } from '../../state/governance/hooks'
|
||||
import { CardBGImage, CardNoise, CardSection, DataCard } from '../../components/earn/styled'
|
||||
import {
|
||||
ProposalData,
|
||||
ProposalState,
|
||||
useAllProposalData,
|
||||
useUserDelegatee,
|
||||
useUserVotes
|
||||
} from '../../state/governance/hooks'
|
||||
import DelegateModal from '../../components/vote/DelegateModal'
|
||||
import { useTokenBalance } from '../../state/wallet/hooks'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { UNI, ZERO_ADDRESS } from '../../constants'
|
||||
import { JSBI, TokenAmount, ChainId } from '@uniswap/sdk'
|
||||
import { shortenAddress, getEtherscanLink } from '../../utils'
|
||||
import { ChainId, JSBI, TokenAmount } from '@uniswap/sdk'
|
||||
import { getEtherscanLink, shortenAddress } from '../../utils'
|
||||
import Loader from '../../components/Loader'
|
||||
import FormattedCurrencyAmount from '../../components/FormattedCurrencyAmount'
|
||||
import { useModalOpen, useToggleDelegateModal } from '../../state/application/hooks'
|
||||
@@ -223,7 +229,7 @@ export default function Vote() {
|
||||
<Proposal as={Link} to={'/vote/' + p.id} key={i}>
|
||||
<ProposalNumber>{p.id}</ProposalNumber>
|
||||
<ProposalTitle>{p.title}</ProposalTitle>
|
||||
<ProposalStatus status={p.status}>{p.status}</ProposalStatus>
|
||||
<ProposalStatus status={p.status}>{ProposalState[p.status]}</ProposalStatus>
|
||||
</Proposal>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -1,29 +1,25 @@
|
||||
import styled from 'styled-components'
|
||||
import { ProposalState } from '../../state/governance/hooks'
|
||||
|
||||
const handleColorType = (status?: any, theme?: any) => {
|
||||
const handleColorType = (status?: ProposalState, theme?: any) => {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
case ProposalState.Pending:
|
||||
case ProposalState.Active:
|
||||
return theme.blue1
|
||||
case 'active':
|
||||
return theme.blue1
|
||||
case 'succeeded':
|
||||
case ProposalState.Succeeded:
|
||||
case ProposalState.Executed:
|
||||
return theme.green1
|
||||
case 'defeated':
|
||||
case ProposalState.Defeated:
|
||||
return theme.red1
|
||||
case 'queued':
|
||||
return theme.text3
|
||||
case 'executed':
|
||||
return theme.green1
|
||||
case 'canceled':
|
||||
return theme.text3
|
||||
case 'expired':
|
||||
return theme.text3
|
||||
case ProposalState.Queued:
|
||||
case ProposalState.Canceled:
|
||||
case ProposalState.Expired:
|
||||
default:
|
||||
return theme.text3
|
||||
}
|
||||
}
|
||||
|
||||
export const ProposalStatus = styled.span<{ status: string }>`
|
||||
export const ProposalStatus = styled.span<{ status: ProposalState }>`
|
||||
font-size: 0.825rem;
|
||||
font-weight: 600;
|
||||
padding: 0.5rem;
|
||||
|
||||
80
src/service-worker.ts
Normal file
80
src/service-worker.ts
Normal 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.
|
||||
139
src/serviceWorkerRegistration.ts
Normal file
139
src/serviceWorkerRegistration.ts
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -22,7 +22,7 @@ export interface ProposalData {
|
||||
title: string
|
||||
description: string
|
||||
proposer: string
|
||||
status: string
|
||||
status: ProposalState
|
||||
forCount: number
|
||||
againstCount: number
|
||||
startBlock: number
|
||||
@@ -30,9 +30,16 @@ export interface ProposalData {
|
||||
details: ProposalDetail[]
|
||||
}
|
||||
|
||||
const enumerateProposalState = (state: number) => {
|
||||
const proposalStates = ['pending', 'active', 'canceled', 'defeated', 'succeeded', 'queued', 'expired', 'executed']
|
||||
return proposalStates[state]
|
||||
export enum ProposalState {
|
||||
Undetermined = -1,
|
||||
Pending,
|
||||
Active,
|
||||
Canceled,
|
||||
Defeated,
|
||||
Succeeded,
|
||||
Queued,
|
||||
Expired,
|
||||
Executed
|
||||
}
|
||||
|
||||
// get count of all proposals made
|
||||
@@ -127,7 +134,7 @@ export function useAllProposalData() {
|
||||
title: description?.split(/# |\n/g)[1] || 'Untitled',
|
||||
description: description || 'No description.',
|
||||
proposer: allProposals[i]?.result?.proposer,
|
||||
status: enumerateProposalState(allProposalStates[i]?.result?.[0]) ?? 'Undetermined',
|
||||
status: allProposalStates[i]?.result?.[0] ?? ProposalState.Undetermined,
|
||||
forCount: parseFloat(ethers.utils.formatUnits(allProposals[i]?.result?.forVotes.toString(), 18)),
|
||||
againstCount: parseFloat(ethers.utils.formatUnits(allProposals[i]?.result?.againstVotes.toString(), 18)),
|
||||
startBlock: parseInt(allProposals[i]?.result?.startBlock?.toString()),
|
||||
|
||||
@@ -61,7 +61,10 @@ export function listToTokenMap(list: TokenList): TokenAddressMap {
|
||||
})
|
||||
?.filter((x): x is TagInfo => Boolean(x)) ?? []
|
||||
const token = new WrappedTokenInfo(tokenInfo, tags)
|
||||
if (tokenMap[token.chainId][token.address] !== undefined) throw Error('Duplicate tokens.')
|
||||
if (tokenMap[token.chainId][token.address] !== undefined) {
|
||||
console.error(new Error(`Duplicate token! ${token.address}`))
|
||||
return tokenMap
|
||||
}
|
||||
return {
|
||||
...tokenMap,
|
||||
[token.chainId]: {
|
||||
@@ -103,10 +106,8 @@ function combineMaps(map1: TokenAddressMap, map2: TokenAddressMap): TokenAddress
|
||||
// merge tokens contained within lists from urls
|
||||
function useCombinedTokenMapFromUrls(urls: string[] | undefined): TokenAddressMap {
|
||||
const lists = useAllLists()
|
||||
|
||||
return useMemo(() => {
|
||||
if (!urls) return EMPTY_LIST
|
||||
|
||||
return (
|
||||
urls
|
||||
.slice()
|
||||
|
||||
@@ -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
|
||||
}, {})
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user