├── .env
├── .env.production
├── .eslintrc.json
├── .github
└── workflows
│ └── lint.yml
├── .gitignore
├── .nvmrc
├── .prettierrc
├── .vscode
├── launch.json
└── settings.json
├── .yarnrc
├── LICENSE
├── README.md
├── package.json
├── public
├── favicon.ico
├── index.html
├── loading.gif
└── manifest.json
├── src
├── App.js
├── Theme
│ └── index.js
├── apollo
│ ├── client.js
│ └── queries.js
├── assets
│ ├── arrows.svg
│ ├── eth.png
│ ├── logo.svg
│ ├── logo2.svg
│ ├── logo_white.svg
│ ├── placeholder.png
│ ├── unicorn.svg
│ ├── wordmark.svg
│ └── wordmark_white.svg
├── components
│ ├── AccountSearch
│ │ └── index.js
│ ├── Attribution
│ │ └── index.js
│ ├── ButtonStyled
│ │ └── index.js
│ ├── CandleChart
│ │ └── index.js
│ ├── Chart
│ │ └── index.js
│ ├── Checkbox
│ │ └── index.js
│ ├── Column
│ │ └── index.js
│ ├── Copy
│ │ └── index.js
│ ├── CurrencySelect
│ │ └── index.js
│ ├── Dashboard
│ │ └── index.js
│ ├── DoubleLogo
│ │ └── index.js
│ ├── DropdownSelect
│ │ └── index.js
│ ├── Emoji
│ │ └── index.js
│ ├── Footer
│ │ └── index.js
│ ├── FormattedName
│ │ └── index.js
│ ├── GlobalChart
│ │ └── index.js
│ ├── GlobalStats
│ │ └── index.js
│ ├── HoverText
│ │ └── index.tsx
│ ├── LPList
│ │ └── index.js
│ ├── Link
│ │ └── index.js
│ ├── LocalLoader
│ │ └── index.js
│ ├── Meta.tsx
│ ├── MiningPositionList
│ │ └── index.js
│ ├── PairChart
│ │ └── index.js
│ ├── PairList
│ │ └── index.js
│ ├── PairReturnsChart
│ │ └── index.js
│ ├── Panel
│ │ └── index.js
│ ├── PinnedData
│ │ └── index.js
│ ├── Popover
│ │ └── index.tsx
│ ├── PositionList
│ │ └── index.js
│ ├── QuestionHelper
│ │ └── index.tsx
│ ├── Row
│ │ └── index.js
│ ├── Search
│ │ └── index.js
│ ├── Select
│ │ ├── index.js
│ │ ├── popout.js
│ │ └── styles.js
│ ├── SideNav
│ │ └── index.js
│ ├── Title
│ │ └── index.js
│ ├── Toggle
│ │ └── index.tsx
│ ├── TokenChart
│ │ └── index.js
│ ├── TokenList
│ │ └── index.js
│ ├── TokenLogo
│ │ └── index.js
│ ├── TradingviewChart
│ │ └── index.js
│ ├── TxnList
│ │ └── index.js
│ ├── UniPrice
│ │ └── index.js
│ ├── UserChart
│ │ └── index.js
│ ├── Warning
│ │ └── index.js
│ ├── analytics
│ │ └── GoogleAnalyticsReporter.jsx
│ └── index.js
├── constants
│ └── index.js
├── contexts
│ ├── Application.js
│ ├── GlobalData.js
│ ├── LocalStorage.js
│ ├── PairData.js
│ ├── TokenData.js
│ ├── User.js
│ └── V1Data.js
├── hooks
│ └── index.ts
├── index.js
├── pages
│ ├── AccountLookup.js
│ ├── AccountPage.js
│ ├── AllPairsPage.js
│ ├── AllTokensPage.js
│ ├── GlobalPage.js
│ ├── PairPage.js
│ └── TokenPage.js
├── react-app-env.d.ts
└── utils
│ ├── data.ts
│ ├── index.js
│ ├── returns.ts
│ └── tokenLists.ts
├── tsconfig.json
├── tsconfig.strict.json
├── webpack.config.js
└── yarn.lock
/.env:
--------------------------------------------------------------------------------
1 | REACT_APP_GOOGLE_ANALYTICS_ID="UA-128182339-5"
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
1 | REACT_APP_GOOGLE_ANALYTICS_ID="UA-128182339-5"
2 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@typescript-eslint/parser",
3 | "parserOptions": {
4 | "ecmaVersion": 2020,
5 | "sourceType": "module",
6 | "ecmaFeatures": {
7 | // Allows for the parsing of JSX
8 | "jsx": true
9 | }
10 | },
11 | "ignorePatterns": ["node_modules/**/*"],
12 | "settings": {
13 | "react": {
14 | "version": "detect"
15 | }
16 | },
17 | "extends": [
18 | "plugin:react/recommended",
19 | "plugin:@typescript-eslint/recommended",
20 | "plugin:react-hooks/recommended",
21 | "prettier/@typescript-eslint",
22 | "plugin:prettier/recommended"
23 | ],
24 | "rules": {
25 | "@typescript-eslint/explicit-function-return-type": "off",
26 | "prettier/prettier": "error",
27 | "@typescript-eslint/no-explicit-any": "off"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: Lint
2 |
3 | on:
4 | push:
5 | branches:
6 | - v2
7 | pull_request:
8 |
9 | jobs:
10 | run-linters:
11 | name: Run linters
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - name: Check out Git repository
16 | uses: actions/checkout@v2
17 |
18 | - name: Set up node
19 | uses: actions/setup-node@v1
20 | with:
21 | node-version: 16
22 | always-auth: true
23 | registry-url: https://registry.npmjs.org
24 |
25 | - name: Set output of cache
26 | id: yarn-cache
27 | run: echo "::set-output name=dir::$(yarn cache dir)"
28 |
29 | - name: Node dependency cache
30 | uses: actions/cache@v1
31 | with:
32 | path: ${{ steps.yarn-cache.outputs.dir }}
33 | key: yarn-${{ hashFiles('**/yarn.lock') }}
34 | restore-keys: |
35 | yarn-
36 |
37 | - name: Install dependencies
38 | run: yarn install --frozen-lockfile
39 |
40 | - name: Run linters
41 | uses: wearerequired/lint-action@77d70b9a07ecb93bc98dc46dc27d96c4f004d035
42 | with:
43 | github_token: ${{ secrets.github_token }}
44 | eslint: true
45 | eslint_extensions: js,jsx,ts,tsx,json
46 | auto_fix: true
47 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 |
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v16
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "singleQuote": true,
4 | "printWidth": 120
5 | }
6 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "pwa-chrome",
9 | "request": "launch",
10 | "name": "Launch Chrome against localhost",
11 | "url": "http://localhost:8080",
12 | "webRoot": "${workspaceFolder}"
13 | }
14 | ]
15 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true
3 | }
--------------------------------------------------------------------------------
/.yarnrc:
--------------------------------------------------------------------------------
1 | ignore-scripts true
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Uniswap Info (V1 + V2)
2 |
3 | [](https://github.com/Uniswap/uniswap-info/actions?query=workflow%3ALint)
4 | [](https://github.com/prettier/prettier)
5 |
6 | Analytics site for the [Uniswap Protocol](https://uniswap.org).
7 |
8 | Includes support for Uniswap V1 and V2. For Uniswap V3 info see https://github.com/Uniswap/v3-info
9 |
10 | ### To Start Development
11 |
12 | ###### Installing dependencies
13 | ```bash
14 | yarn
15 | ```
16 |
17 | ###### Running locally
18 | ```bash
19 | yarn start
20 | ```
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@uniswap/info",
3 | "private": true,
4 | "homepage": "https://info.uniswap.org",
5 | "devDependencies": {
6 | "@popperjs/core": "^2.4.2",
7 | "@reach/portal": "^0.10.3",
8 | "@reach/router": "^1.2.1",
9 | "@types/jest": "^26.0.0",
10 | "@types/node": "^16.x",
11 | "@types/react": "^16.9.36",
12 | "@types/react-dom": "^16.9.8",
13 | "@types/styled-components": "^5.1.0",
14 | "@typescript-eslint/eslint-plugin": "^4.4.0",
15 | "@typescript-eslint/parser": "^4.4.0",
16 | "@uniswap/token-lists": "^1.0.0-beta.15",
17 | "ajv": "^6.12.4",
18 | "animated-number-react": "^0.1.1",
19 | "apollo-cache-inmemory": "^1.6.3",
20 | "apollo-client": "^2.6.4",
21 | "apollo-link-http": "^1.5.16",
22 | "bignumber.js": "^9.0.0",
23 | "color-contrast-checker": "^1.5.0",
24 | "copy-to-clipboard": "^3.3.1",
25 | "dayjs": "^1.8.16",
26 | "decimal.js-light": "^2.5.0",
27 | "es-abstract": "^1.14.2",
28 | "eslint": "^6.8.0",
29 | "eslint-config-prettier": "^6.12.0",
30 | "eslint-plugin-prettier": "^3.1.4",
31 | "eslint-plugin-react": "^7.21.3",
32 | "eslint-plugin-react-hooks": "^4.1.2",
33 | "ethers": "^4.0.39",
34 | "export-to-csv": "^0.2.1",
35 | "feather-icons": "^4.24.1",
36 | "graphql": "^14.5.6",
37 | "graphql-tag": "^2.10.1",
38 | "jazzicon": "^1.5.0",
39 | "lightweight-charts": "^3.1.3",
40 | "node-vibrant": "^3.1.5",
41 | "numeral": "^2.0.6",
42 | "polished": "^3.4.4",
43 | "prettier": "^2.1.2",
44 | "react": "^16.9.0",
45 | "react-apollo": "^3.1.1",
46 | "react-device-detect": "^1.9.10",
47 | "react-dom": "^16.9.0",
48 | "react-feather": "^2.0.3",
49 | "react-ga": "^3.1.2",
50 | "react-iframe": "^1.8.0",
51 | "react-parallax": "^2.2.4",
52 | "react-popper": "^2.2.3",
53 | "react-resize-observer": "^1.1.1",
54 | "react-router-dom": "^5.2.0",
55 | "react-scripts": "^3.1.1",
56 | "react-scroll": "^1.8.0",
57 | "react-scroll-parallax": "^2.2.0",
58 | "react-select": "^3.0.7",
59 | "react-spring": "^8.0.27",
60 | "react-springy-parallax": "^1.3.0",
61 | "react-switch": "^5.0.1",
62 | "react-use": "^12.2.0",
63 | "rebass": "^4.0.7",
64 | "recharts": "^1.7.1",
65 | "recharts-fork": "^1.7.2",
66 | "styled-components": "^4.3.2",
67 | "toformat": "^2.0.0",
68 | "typeface-inter": "^3.10.0",
69 | "typescript": "^3.9.5",
70 | "unstated": "^2.1.1",
71 | "wcag-contrast": "^3.0.0"
72 | },
73 | "scripts": {
74 | "start": "react-scripts start",
75 | "build": "react-scripts build",
76 | "test": "react-scripts test",
77 | "eject": "react-scripts eject"
78 | },
79 | "browserslist": {
80 | "production": [
81 | ">0.2%",
82 | "not dead",
83 | "not op_mini all"
84 | ],
85 | "development": [
86 | "last 1 chrome version",
87 | "last 1 firefox version",
88 | "last 1 safari version"
89 | ]
90 | },
91 | "license": "GPL-3.0-or-later",
92 | "dependencies": {
93 | "react-helmet": "^6.1.0"
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Uniswap/info/a668245cfcb786f57af67fb5e6d999d7b11b1f05/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
27 | Uniswap Info
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Uniswap/info/a668245cfcb786f57af67fb5e6d999d7b11b1f05/public/loading.gif
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Uniswap Info",
3 | "name": "View statistics for Uniswap exchanges.",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import styled from 'styled-components'
3 | import { ApolloProvider } from 'react-apollo'
4 | import { client } from './apollo/client'
5 | import { Route, Switch, BrowserRouter, Redirect } from 'react-router-dom'
6 | import GlobalPage from './pages/GlobalPage'
7 | import TokenPage from './pages/TokenPage'
8 | import PairPage from './pages/PairPage'
9 | import Link from './components/Link'
10 | import { useGlobalData, useGlobalChartData } from './contexts/GlobalData'
11 | import { isAddress } from './utils'
12 | import AccountPage from './pages/AccountPage'
13 | import AllTokensPage from './pages/AllTokensPage'
14 | import AllPairsPage from './pages/AllPairsPage'
15 | import PinnedData from './components/PinnedData'
16 |
17 | import SideNav from './components/SideNav'
18 | import AccountLookup from './pages/AccountLookup'
19 | import Meta from './components/Meta'
20 | import LocalLoader from './components/LocalLoader'
21 | import { useLatestBlocks } from './contexts/Application'
22 | import GoogleAnalyticsReporter from './components/analytics/GoogleAnalyticsReporter'
23 | import { PAIR_BLACKLIST, TOKEN_BLACKLIST } from './constants'
24 |
25 | const AppWrapper = styled.div`
26 | position: relative;
27 | width: 100%;
28 | `
29 | const ContentWrapper = styled.div`
30 | display: grid;
31 | grid-template-columns: ${({ open }) => (open ? '220px 1fr 200px' : '220px 1fr 64px')};
32 |
33 | @media screen and (max-width: 1400px) {
34 | grid-template-columns: 220px 1fr;
35 | }
36 |
37 | @media screen and (max-width: 1080px) {
38 | grid-template-columns: 1fr;
39 | max-width: 100vw;
40 | overflow: hidden;
41 | grid-gap: 0;
42 | }
43 | `
44 |
45 | const Right = styled.div`
46 | position: fixed;
47 | right: 0;
48 | bottom: 0rem;
49 | z-index: 99;
50 | width: ${({ open }) => (open ? '220px' : '64px')};
51 | height: ${({ open }) => (open ? 'fit-content' : '64px')};
52 | overflow: auto;
53 | background-color: ${({ theme }) => theme.bg1};
54 | @media screen and (max-width: 1400px) {
55 | display: none;
56 | }
57 | `
58 |
59 | const Center = styled.div`
60 | height: 100%;
61 | z-index: 9999;
62 | transition: width 0.25s ease;
63 | background-color: ${({ theme }) => theme.onlyLight};
64 | `
65 |
66 | const BannerWrapper = styled.div`
67 | width: 100%;
68 | display: flex;
69 | justify-content: center;
70 | `
71 |
72 | const WarningBanner = styled.div`
73 | background-color: #ff6871;
74 | padding: 1.5rem;
75 | color: white;
76 | width: 100%;
77 | text-align: center;
78 | font-weight: 500;
79 | `
80 |
81 | const UrlBanner = styled.div`
82 | background-color: #ff007a;
83 | padding: 1rem;
84 | color: white;
85 | width: 100%;
86 | text-align: center;
87 | font-weight: 500;
88 | `
89 |
90 | const Decorator = styled.span`
91 | text-decoration: underline;
92 | `
93 |
94 | /**
95 | * Wrap the component with the header and sidebar pinned tab
96 | */
97 | const LayoutWrapper = ({ children, savedOpen, setSavedOpen }) => {
98 | return (
99 | <>
100 |
101 |
102 |
103 | {children}
104 |
105 |
106 |
107 |
108 | >
109 | )
110 | }
111 |
112 | const BLOCK_DIFFERENCE_THRESHOLD = 30
113 |
114 | function App() {
115 | const [savedOpen, setSavedOpen] = useState(false)
116 |
117 | const globalData = useGlobalData()
118 | const globalChartData = useGlobalChartData()
119 | const [latestBlock, headBlock] = useLatestBlocks()
120 |
121 | // show warning
122 | const showWarning = headBlock && latestBlock ? headBlock - latestBlock > BLOCK_DIFFERENCE_THRESHOLD : false
123 |
124 | return (
125 |
126 |
127 |
128 |
129 | {`info.uniswap.org is being deprecated on June 11th. Explore the new combined V2 and V3 analytics at `}
130 |
131 | app.uniswap.org
132 | {' '}
133 |
134 |
135 | {showWarning && (
136 |
137 |
138 | {`Warning: The data on this site has only synced to Ethereum block ${latestBlock} (out of ${headBlock}). Please check back soon.`}
139 |
140 |
141 | )}
142 | {globalData &&
143 | Object.keys(globalData).length > 0 &&
144 | globalChartData &&
145 | Object.keys(globalChartData).length > 0 ? (
146 |
147 |
148 |
149 | {
154 | if (
155 | isAddress(match.params.tokenAddress.toLowerCase()) &&
156 | !Object.keys(TOKEN_BLACKLIST).includes(match.params.tokenAddress.toLowerCase())
157 | ) {
158 | return (
159 |
160 |
161 |
162 | )
163 | } else {
164 | return
165 | }
166 | }}
167 | />
168 | {
173 | if (
174 | isAddress(match.params.pairAddress.toLowerCase()) &&
175 | !Object.keys(PAIR_BLACKLIST).includes(match.params.pairAddress.toLowerCase())
176 | ) {
177 | return (
178 |
179 |
180 |
181 | )
182 | } else {
183 | return
184 | }
185 | }}
186 | />
187 | {
192 | if (isAddress(match.params.accountAddress.toLowerCase())) {
193 | return (
194 |
195 |
196 |
197 | )
198 | } else {
199 | return
200 | }
201 | }}
202 | />
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 | ) : (
232 |
233 | )}
234 |
235 |
236 | )
237 | }
238 |
239 | export default App
240 |
--------------------------------------------------------------------------------
/src/Theme/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { ThemeProvider as StyledComponentsThemeProvider, createGlobalStyle } from 'styled-components'
3 | import { useDarkModeManager } from '../contexts/LocalStorage'
4 | import styled from 'styled-components'
5 | import { Text } from 'rebass'
6 |
7 | export default function ThemeProvider({ children }) {
8 | const [darkMode] = useDarkModeManager()
9 |
10 | return {children}
11 | }
12 |
13 | const theme = (darkMode, color) => ({
14 | customColor: color,
15 | textColor: darkMode ? color : 'black',
16 |
17 | panelColor: darkMode ? 'rgba(255, 255, 255, 0)' : 'rgba(255, 255, 255, 0)',
18 | backgroundColor: darkMode ? '#212429' : '#F7F8FA',
19 |
20 | uniswapPink: darkMode ? '#ff007a' : 'black',
21 |
22 | concreteGray: darkMode ? '#292C2F' : '#FAFAFA',
23 | inputBackground: darkMode ? '#1F1F1F' : '#FAFAFA',
24 | shadowColor: darkMode ? '#000' : '#2F80ED',
25 | mercuryGray: darkMode ? '#333333' : '#E1E1E1',
26 |
27 | text1: darkMode ? '#FAFAFA' : '#1F1F1F',
28 | text2: darkMode ? '#C3C5CB' : '#565A69',
29 | text3: darkMode ? '#6C7284' : '#888D9B',
30 | text4: darkMode ? '#565A69' : '#C3C5CB',
31 | text5: darkMode ? '#2C2F36' : '#EDEEF2',
32 |
33 | // special case text types
34 | white: '#FFFFFF',
35 |
36 | // backgrounds / greys
37 | bg1: darkMode ? '#212429' : '#FAFAFA',
38 | bg2: darkMode ? '#2C2F36' : '#F7F8FA',
39 | bg3: darkMode ? '#40444F' : '#EDEEF2',
40 | bg4: darkMode ? '#565A69' : '#CED0D9',
41 | bg5: darkMode ? '#565A69' : '#888D9B',
42 | bg6: darkMode ? '#000' : '#FFFFFF',
43 |
44 | //specialty colors
45 | modalBG: darkMode ? 'rgba(0,0,0,0.85)' : 'rgba(0,0,0,0.6)',
46 | advancedBG: darkMode ? 'rgba(0,0,0,0.1)' : 'rgba(255,255,255,0.4)',
47 | onlyLight: darkMode ? '#22242a' : 'transparent',
48 | divider: darkMode ? 'rgba(43, 43, 43, 0.435)' : 'rgba(43, 43, 43, 0.035)',
49 |
50 | //primary colors
51 | primary1: darkMode ? '#2172E5' : '#ff007a',
52 | primary2: darkMode ? '#3680E7' : '#FF8CC3',
53 | primary3: darkMode ? '#4D8FEA' : '#FF99C9',
54 | primary4: darkMode ? '#376bad70' : '#F6DDE8',
55 | primary5: darkMode ? '#153d6f70' : '#FDEAF1',
56 |
57 | // color text
58 | primaryText1: darkMode ? '#6da8ff' : '#ff007a',
59 |
60 | // secondary colors
61 | secondary1: darkMode ? '#2172E5' : '#ff007a',
62 | secondary2: darkMode ? '#17000b26' : '#F6DDE8',
63 | secondary3: darkMode ? '#17000b26' : '#FDEAF1',
64 |
65 | shadow1: darkMode ? '#000' : '#2F80ED',
66 |
67 | // other
68 | red1: '#FF6871',
69 | green1: '#27AE60',
70 | yellow1: '#FFE270',
71 | yellow2: '#F3841E',
72 | link: '#2172E5',
73 | blue: '2f80ed',
74 |
75 | background: darkMode ? 'black' : `radial-gradient(50% 50% at 50% 50%, #ff007a30 0%, #fff 0%)`,
76 | })
77 |
78 | const TextWrapper = styled(Text)`
79 | color: ${({ color, theme }) => theme[color]};
80 | `
81 |
82 | export const TYPE = {
83 | main(props) {
84 | return
85 | },
86 |
87 | body(props) {
88 | return
89 | },
90 |
91 | small(props) {
92 | return
93 | },
94 |
95 | header(props) {
96 | return
97 | },
98 |
99 | largeHeader(props) {
100 | return
101 | },
102 |
103 | light(props) {
104 | return
105 | },
106 |
107 | pink(props) {
108 | return
109 | },
110 | }
111 |
112 | export const Hover = styled.div`
113 | :hover {
114 | cursor: pointer;
115 | }
116 | `
117 |
118 | export const Link = styled.a.attrs({
119 | target: '_blank',
120 | rel: 'noopener noreferrer',
121 | })`
122 | text-decoration: none;
123 | cursor: pointer;
124 | color: ${({ theme }) => theme.primary1};
125 | font-weight: 500;
126 | :hover {
127 | text-decoration: underline;
128 | }
129 | :focus {
130 | outline: none;
131 | text-decoration: underline;
132 | }
133 | :active {
134 | text-decoration: none;
135 | }
136 | `
137 |
138 | export const ThemedBackground = styled.div`
139 | position: absolute;
140 | top: 0;
141 | left: 0;
142 | right: 0;
143 | pointer-events: none;
144 | max-width: 100vw !important;
145 | height: 200vh;
146 | mix-blend-mode: color;
147 | background: ${({ backgroundColor }) =>
148 | `radial-gradient(50% 50% at 50% 50%, ${backgroundColor} 0%, rgba(255, 255, 255, 0) 100%)`};
149 | position: absolute;
150 | top: 0px;
151 | left: 0px;
152 | /* z-index: ; */
153 |
154 | transform: translateY(-110vh);
155 | `
156 |
157 | export const GlobalStyle = createGlobalStyle`
158 | @import url('https://rsms.me/inter/inter.css');
159 | html { font-family: 'Inter', sans-serif; }
160 | @supports (font-variation-settings: normal) {
161 | html { font-family: 'Inter var', sans-serif; }
162 | }
163 |
164 | html,
165 | body {
166 | margin: 0;
167 | padding: 0;
168 | width: 100%;
169 | height: 100%;
170 | font-size: 14px;
171 | background-color: ${({ theme }) => theme.bg6};
172 | }
173 |
174 | a {
175 | text-decoration: none;
176 |
177 | :hover {
178 | text-decoration: none
179 | }
180 | }
181 |
182 |
183 | .three-line-legend {
184 | width: 100%;
185 | height: 70px;
186 | position: absolute;
187 | padding: 8px;
188 | font-size: 12px;
189 | color: #20262E;
190 | background-color: rgba(255, 255, 255, 0.23);
191 | text-align: left;
192 | z-index: 10;
193 | pointer-events: none;
194 | }
195 |
196 | .three-line-legend-dark {
197 | width: 100%;
198 | height: 70px;
199 | position: absolute;
200 | padding: 8px;
201 | font-size: 12px;
202 | color: white;
203 | background-color: rgba(255, 255, 255, 0.23);
204 | text-align: left;
205 | z-index: 10;
206 | pointer-events: none;
207 | }
208 |
209 | @media screen and (max-width: 800px) {
210 | .three-line-legend {
211 | display: none !important;
212 | }
213 | }
214 |
215 | .tv-lightweight-charts{
216 | width: 100% !important;
217 |
218 |
219 | & > * {
220 | width: 100% !important;
221 | }
222 | }
223 |
224 |
225 | html {
226 | font-size: 1rem;
227 | font-variant: none;
228 | color: 'black';
229 | -webkit-font-smoothing: antialiased;
230 | -moz-osx-font-smoothing: grayscale;
231 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
232 | height: 100%;
233 | }
234 | `
235 |
--------------------------------------------------------------------------------
/src/apollo/client.js:
--------------------------------------------------------------------------------
1 | import { ApolloClient } from 'apollo-client'
2 | import { InMemoryCache } from 'apollo-cache-inmemory'
3 | import { HttpLink } from 'apollo-link-http'
4 |
5 | export const client = new ApolloClient({
6 | link: new HttpLink({
7 | uri: 'https://api.thegraph.com/subgraphs/name/ianlapham/uniswap-v2-dev',
8 | }),
9 | cache: new InMemoryCache(),
10 | shouldBatch: true,
11 | })
12 |
13 | export const healthClient = new ApolloClient({
14 | link: new HttpLink({
15 | uri: 'https://api.thegraph.com/index-node/graphql',
16 | }),
17 | cache: new InMemoryCache(),
18 | shouldBatch: true,
19 | })
20 |
21 | export const v1Client = new ApolloClient({
22 | link: new HttpLink({
23 | uri: 'https://api.thegraph.com/subgraphs/name/ianlapham/uniswap',
24 | }),
25 | cache: new InMemoryCache(),
26 | shouldBatch: true,
27 | })
28 |
29 | export const stakingClient = new ApolloClient({
30 | link: new HttpLink({
31 | uri: 'https://api.thegraph.com/subgraphs/name/way2rach/talisman',
32 | }),
33 | cache: new InMemoryCache(),
34 | shouldBatch: true,
35 | })
36 |
37 | export const blockClient = new ApolloClient({
38 | link: new HttpLink({
39 | uri: 'https://api.thegraph.com/subgraphs/name/blocklytics/ethereum-blocks',
40 | }),
41 | cache: new InMemoryCache(),
42 | })
43 |
--------------------------------------------------------------------------------
/src/assets/eth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Uniswap/info/a668245cfcb786f57af67fb5e6d999d7b11b1f05/src/assets/eth.png
--------------------------------------------------------------------------------
/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/src/assets/logo_white.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/src/assets/placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Uniswap/info/a668245cfcb786f57af67fb5e6d999d7b11b1f05/src/assets/placeholder.png
--------------------------------------------------------------------------------
/src/assets/unicorn.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/src/components/AccountSearch/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import 'feather-icons'
3 | import { withRouter } from 'react-router-dom'
4 | import styled from 'styled-components'
5 | import { ButtonLight, ButtonFaded } from '../ButtonStyled'
6 | import { AutoRow, RowBetween } from '../Row'
7 | import { isAddress } from '../../utils'
8 | import { useSavedAccounts } from '../../contexts/LocalStorage'
9 | import { AutoColumn } from '../Column'
10 | import { TYPE } from '../../Theme'
11 | import { Hover, StyledIcon } from '..'
12 | import Panel from '../Panel'
13 | import { Divider } from '..'
14 | import { Flex } from 'rebass'
15 |
16 | import { X } from 'react-feather'
17 |
18 | const Wrapper = styled.div`
19 | display: flex;
20 | flex-direction: row;
21 | align-items: center;
22 | justify-content: flex-end;
23 | width: 100%;
24 | border-radius: 12px;
25 | `
26 |
27 | const Input = styled.input`
28 | position: relative;
29 | display: flex;
30 | align-items: center;
31 | width: 100%;
32 | white-space: nowrap;
33 | background: none;
34 | border: none;
35 | outline: none;
36 | padding: 12px 16px;
37 | border-radius: 12px;
38 | color: ${({ theme }) => theme.text1};
39 | background-color: ${({ theme }) => theme.bg1};
40 | font-size: 16px;
41 | margin-right: 1rem;
42 | border: 1px solid ${({ theme }) => theme.bg3};
43 |
44 | ::placeholder {
45 | color: ${({ theme }) => theme.text3};
46 | font-size: 14px;
47 | }
48 |
49 | @media screen and (max-width: 640px) {
50 | ::placeholder {
51 | font-size: 1rem;
52 | }
53 | }
54 | `
55 |
56 | const AccountLink = styled.span`
57 | display: flex;
58 | cursor: pointer;
59 | color: ${({ theme }) => theme.link};
60 | font-size: 14px;
61 | font-weight: 500;
62 | `
63 |
64 | const DashGrid = styled.div`
65 | display: grid;
66 | grid-gap: 1em;
67 | grid-template-columns: 1fr;
68 | grid-template-areas: 'account';
69 | padding: 0 4px;
70 |
71 | > * {
72 | justify-content: flex-end;
73 | }
74 | `
75 |
76 | function AccountSearch({ history, small }) {
77 | const [accountValue, setAccountValue] = useState()
78 | const [savedAccounts, addAccount, removeAccount] = useSavedAccounts()
79 |
80 | function handleAccountSearch() {
81 | if (isAddress(accountValue)) {
82 | history.push('/account/' + accountValue)
83 | if (!savedAccounts.includes(accountValue)) {
84 | addAccount(accountValue)
85 | }
86 | }
87 | }
88 |
89 | return (
90 |
91 | {!small && (
92 | <>
93 |
94 |
95 | {
98 | setAccountValue(e.target.value)
99 | }}
100 | />
101 |
102 | Load Account Details
103 |
104 | >
105 | )}
106 |
107 |
108 | {!small && (
109 |
110 |
111 | Saved Accounts
112 |
113 |
114 | {savedAccounts?.length > 0 ? (
115 | savedAccounts.map((account) => {
116 | return (
117 |
118 | history.push('/account/' + account)}
122 | >
123 | {account?.slice(0, 42)}
124 | {
126 | e.stopPropagation()
127 | removeAccount(account)
128 | }}
129 | >
130 |
131 |
132 |
133 |
134 |
135 |
136 | )
137 | })
138 | ) : (
139 | No saved accounts
140 | )}
141 |
142 | )}
143 |
144 | {small && (
145 | <>
146 | {'Accounts'}
147 | {savedAccounts?.length > 0 ? (
148 | savedAccounts.map((account) => {
149 | return (
150 |
151 | history.push('/account/' + account)}>
152 | {small ? (
153 | {account?.slice(0, 6) + '...' + account?.slice(38, 42)}
154 | ) : (
155 | {account?.slice(0, 42)}
156 | )}
157 |
158 | removeAccount(account)}>
159 |
160 |
161 |
162 |
163 |
164 | )
165 | })
166 | ) : (
167 | No pinned wallets
168 | )}
169 | >
170 | )}
171 |
172 |
173 | )
174 | }
175 |
176 | export default withRouter(AccountSearch)
177 |
--------------------------------------------------------------------------------
/src/components/Attribution/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Attribution = () => (
4 |
5 |
6 | Github
7 | {' '}
8 | |{' '}
9 |
10 | Uniswap
11 | {' '}
12 | |{' '}
13 |
14 | GIF
15 |
16 |
17 | )
18 |
19 | export default Attribution
20 |
--------------------------------------------------------------------------------
/src/components/ButtonStyled/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Button as RebassButton } from 'rebass/styled-components'
3 | import styled from 'styled-components'
4 | import { Plus, ChevronDown, ChevronUp } from 'react-feather'
5 | import { darken, transparentize } from 'polished'
6 | import { RowBetween } from '../Row'
7 | import { StyledIcon } from '..'
8 |
9 | const Base = styled(RebassButton)`
10 | padding: 8px 12px;
11 | font-size: 0.825rem;
12 | font-weight: 600;
13 | border-radius: 12px;
14 | cursor: pointer;
15 | outline: none;
16 | border: 1px solid transparent;
17 | outline: none;
18 | border-bottom-right-radius: ${({ open }) => open && '0'};
19 | border-bottom-left-radius: ${({ open }) => open && '0'};
20 | `
21 |
22 | const BaseCustom = styled(RebassButton)`
23 | padding: 16px 12px;
24 | font-size: 0.825rem;
25 | font-weight: 400;
26 | border-radius: 12px;
27 | cursor: pointer;
28 | outline: none;
29 | `
30 |
31 | const Dull = styled(Base)`
32 | background-color: rgba(255, 255, 255, 0.15);
33 | border: 1px solid rgba(255, 255, 255, 0.15);
34 | color: black;
35 | height: 100%;
36 | font-weight: 400;
37 | &:hover,
38 | :focus {
39 | background-color: rgba(255, 255, 255, 0.25);
40 | border-color: rgba(255, 255, 255, 0.25);
41 | }
42 | &:focus {
43 | box-shadow: 0 0 0 1pt rgba(255, 255, 255, 0.25);
44 | }
45 | &:active {
46 | background-color: rgba(255, 255, 255, 0.25);
47 | border-color: rgba(255, 255, 255, 0.25);
48 | }
49 | `
50 |
51 | export default function ButtonStyled({ children, ...rest }) {
52 | return {children}
53 | }
54 |
55 | const ContentWrapper = styled.div`
56 | display: flex;
57 | flex-direction: row;
58 | align-items: center;
59 | justify-content: space-between;
60 | `
61 |
62 | export const ButtonLight = styled(Base)`
63 | background-color: ${({ color, theme }) => (color ? transparentize(0.9, color) : transparentize(0.9, theme.primary1))};
64 | color: ${({ color, theme }) => (color ? darken(0.1, color) : theme.primary1)};
65 |
66 | min-width: fit-content;
67 | border-radius: 12px;
68 | white-space: nowrap;
69 |
70 | a {
71 | color: ${({ color, theme }) => (color ? darken(0.1, color) : theme.primary1)};
72 | }
73 |
74 | :hover {
75 | background-color: ${({ color, theme }) =>
76 | color ? transparentize(0.8, color) : transparentize(0.8, theme.primary1)};
77 | }
78 | `
79 |
80 | export function ButtonDropdown({ disabled = false, children, open, ...rest }) {
81 | return (
82 |
83 |
84 | {children}
85 | {open ? (
86 |
87 |
88 |
89 | ) : (
90 |
91 |
92 |
93 | )}
94 |
95 |
96 | )
97 | }
98 |
99 | export const ButtonDark = styled(Base)`
100 | background-color: ${({ color, theme }) => (color ? color : theme.primary1)};
101 | color: white;
102 | width: fit-content;
103 | border-radius: 12px;
104 | white-space: nowrap;
105 |
106 | :hover {
107 | background-color: ${({ color, theme }) => (color ? darken(0.1, color) : darken(0.1, theme.primary1))};
108 | }
109 | `
110 |
111 | export const ButtonFaded = styled(Base)`
112 | background-color: ${({ theme }) => theme.bg2};
113 | color: (255, 255, 255, 0.5);
114 | white-space: nowrap;
115 |
116 | :hover {
117 | opacity: 0.5;
118 | }
119 | `
120 |
121 | export function ButtonPlusDull({ disabled, children, ...rest }) {
122 | return (
123 |
124 |
125 |
126 | {children}
127 |
128 |
129 | )
130 | }
131 |
132 | export function ButtonCustom({ children, bgColor, color, ...rest }) {
133 | return (
134 |
135 | {children}
136 |
137 | )
138 | }
139 |
140 | export const OptionButton = styled.div`
141 | font-weight: 500;
142 | width: fit-content;
143 | white-space: nowrap;
144 | padding: 6px;
145 | border-radius: 6px;
146 | border: 1px solid ${({ theme }) => theme.bg4};
147 | background-color: ${({ active, theme }) => active && theme.bg3};
148 | color: ${({ theme }) => theme.text1};
149 |
150 | :hover {
151 | cursor: ${({ disabled }) => !disabled && 'pointer'};
152 | }
153 | `
154 |
--------------------------------------------------------------------------------
/src/components/CandleChart/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react'
2 | import { createChart, CrosshairMode } from 'lightweight-charts'
3 | import dayjs from 'dayjs'
4 | import { formattedNum } from '../../utils'
5 | import { usePrevious } from 'react-use'
6 | import styled from 'styled-components'
7 | import { Play } from 'react-feather'
8 | import { useDarkModeManager } from '../../contexts/LocalStorage'
9 |
10 | const IconWrapper = styled.div`
11 | position: absolute;
12 | right: 10px;
13 | color: ${({ theme }) => theme.text1}
14 | border-radius: 3px;
15 | height: 16px;
16 | width: 16px;
17 | padding: 0px;
18 | bottom: 10px;
19 | display: flex;
20 | align-items: center;
21 | justify-content: center;
22 | :hover {
23 | cursor: pointer;
24 | opacity: 0.7;
25 | }
26 | `
27 |
28 | const CandleStickChart = ({
29 | data,
30 | width,
31 | height = 300,
32 | base,
33 | margin = true,
34 | valueFormatter = (val) => formattedNum(val, true),
35 | }) => {
36 | // reference for DOM element to create with chart
37 | const ref = useRef()
38 |
39 | const formattedData = data?.map((entry) => {
40 | return {
41 | time: parseFloat(entry.timestamp),
42 | open: parseFloat(entry.open),
43 | low: parseFloat(entry.open),
44 | close: parseFloat(entry.close),
45 | high: parseFloat(entry.close),
46 | }
47 | })
48 |
49 | if (formattedData && formattedData.length > 0) {
50 | formattedData.push({
51 | time: dayjs().unix(),
52 | open: parseFloat(formattedData[formattedData.length - 1].close),
53 | close: parseFloat(base),
54 | low: Math.min(parseFloat(base), parseFloat(formattedData[formattedData.length - 1].close)),
55 | high: Math.max(parseFloat(base), parseFloat(formattedData[formattedData.length - 1].close)),
56 | })
57 | }
58 |
59 | // pointer to the chart object
60 | const [chartCreated, setChartCreated] = useState(false)
61 | const dataPrev = usePrevious(data)
62 |
63 | const [darkMode] = useDarkModeManager()
64 | const textColor = darkMode ? 'white' : 'black'
65 | const previousTheme = usePrevious(darkMode)
66 |
67 | // reset the chart if theme switches
68 | useEffect(() => {
69 | if (chartCreated && previousTheme !== darkMode) {
70 | // remove the tooltip element
71 | let tooltip = document.getElementById('tooltip-id')
72 | let node = document.getElementById('test-id')
73 | node.removeChild(tooltip)
74 | chartCreated.resize(0, 0)
75 | setChartCreated()
76 | }
77 | }, [chartCreated, darkMode, previousTheme])
78 |
79 | useEffect(() => {
80 | if (data !== dataPrev && chartCreated) {
81 | // remove the tooltip element
82 | let tooltip = document.getElementById('tooltip-id')
83 | let node = document.getElementById('test-id')
84 | node.removeChild(tooltip)
85 | chartCreated.resize(0, 0)
86 | setChartCreated()
87 | }
88 | }, [chartCreated, data, dataPrev])
89 |
90 | // if no chart created yet, create one with options and add to DOM manually
91 | useEffect(() => {
92 | if (!chartCreated) {
93 | const chart = createChart(ref.current, {
94 | width: width,
95 | height: height,
96 | layout: {
97 | backgroundColor: 'transparent',
98 | textColor: textColor,
99 | },
100 | grid: {
101 | vertLines: {
102 | color: 'rgba(197, 203, 206, 0.5)',
103 | },
104 | horzLines: {
105 | color: 'rgba(197, 203, 206, 0.5)',
106 | },
107 | },
108 | crosshair: {
109 | mode: CrosshairMode.Normal,
110 | },
111 | rightPriceScale: {
112 | borderColor: 'rgba(197, 203, 206, 0.8)',
113 | visible: true,
114 | },
115 | timeScale: {
116 | borderColor: 'rgba(197, 203, 206, 0.8)',
117 | },
118 | localization: {
119 | priceFormatter: (val) => formattedNum(val),
120 | },
121 | })
122 |
123 | var candleSeries = chart.addCandlestickSeries({
124 | upColor: 'green',
125 | downColor: 'red',
126 | borderDownColor: 'red',
127 | borderUpColor: 'green',
128 | wickDownColor: 'red',
129 | wickUpColor: 'green',
130 | })
131 |
132 | candleSeries.setData(formattedData)
133 |
134 | var toolTip = document.createElement('div')
135 | toolTip.setAttribute('id', 'tooltip-id')
136 | toolTip.className = 'three-line-legend'
137 | ref.current.appendChild(toolTip)
138 | toolTip.style.display = 'block'
139 | toolTip.style.left = (margin ? 116 : 10) + 'px'
140 | toolTip.style.top = 50 + 'px'
141 | toolTip.style.backgroundColor = 'transparent'
142 |
143 | // get the title of the chart
144 | function setLastBarText() {
145 | toolTip.innerHTML = base
146 | ? `` + valueFormatter(base) + '
'
147 | : ''
148 | }
149 | setLastBarText()
150 |
151 | // update the title when hovering on the chart
152 | chart.subscribeCrosshairMove(function (param) {
153 | if (
154 | param === undefined ||
155 | param.time === undefined ||
156 | param.point.x < 0 ||
157 | param.point.x > width ||
158 | param.point.y < 0 ||
159 | param.point.y > height
160 | ) {
161 | setLastBarText()
162 | } else {
163 | var price = param.seriesPrices.get(candleSeries).close
164 | const time = dayjs.unix(param.time).format('MM/DD h:mm A')
165 | toolTip.innerHTML =
166 | `` +
167 | valueFormatter(price) +
168 | `` +
169 | time +
170 | ' UTC' +
171 | '' +
172 | '
'
173 | }
174 | })
175 |
176 | chart.timeScale().fitContent()
177 |
178 | setChartCreated(chart)
179 | }
180 | }, [chartCreated, formattedData, width, height, valueFormatter, base, margin, textColor])
181 |
182 | // responsiveness
183 | useEffect(() => {
184 | if (width) {
185 | chartCreated && chartCreated.resize(width, height)
186 | chartCreated && chartCreated.timeScale().scrollToPosition(0)
187 | }
188 | }, [chartCreated, height, width])
189 |
190 | return (
191 |
192 |
193 |
194 | {
196 | chartCreated && chartCreated.timeScale().fitContent()
197 | }}
198 | />
199 |
200 |
201 | )
202 | }
203 |
204 | export default CandleStickChart
205 |
--------------------------------------------------------------------------------
/src/components/Chart/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 | import { Area, XAxis, YAxis, ResponsiveContainer, Bar, BarChart, CartesianGrid, Tooltip, AreaChart } from 'recharts'
3 | import styled from 'styled-components'
4 | import { useMedia } from 'react-use'
5 | import { toK, toNiceDate, toNiceDateYear } from '../../utils'
6 |
7 | const ChartWrapper = styled.div`
8 | padding-top: 1em;
9 | margin-left: -1.5em;
10 | @media (max-width: 40em) {
11 | margin-left: -1em;
12 | }
13 | `
14 |
15 | const Chart = ({ data, chartOption, currencyUnit, symbol }) => {
16 | const [chartData, setChartData] = useState([])
17 | useEffect(() => {
18 | setChartData([])
19 | setChartData(data)
20 | }, [data, chartOption, currencyUnit])
21 |
22 | const isMobile = useMedia('(max-width: 40em)')
23 | if (chartOption === 'price' && chartData && data) {
24 | return (
25 |
26 |
27 |
28 |
29 | toNiceDate(tick)}
36 | dataKey="dayString"
37 | />
38 | toK(tick)}
44 | axisLine={false}
45 | tickLine={false}
46 | interval="preserveEnd"
47 | minTickGap={80}
48 | yAxisId={2}
49 | />
50 | toK(tick)}
56 | axisLine={false}
57 | tickLine={false}
58 | interval="preserveEnd"
59 | minTickGap={80}
60 | yAxisId={3}
61 | />
62 |
73 |
84 | toK(val, true)}
87 | labelFormatter={(label) => toNiceDateYear(label)}
88 | labelStyle={{ paddingTop: 4 }}
89 | contentStyle={{
90 | padding: '10px 14px',
91 | borderRadius: 10,
92 | borderColor: 'var(--c-zircon)',
93 | }}
94 | wrapperStyle={{ top: -70, left: -10 }}
95 | />
96 |
97 |
98 |
99 | )
100 | }
101 | if (chartOption !== 'volume' && chartData && data) {
102 | return (
103 |
104 |
105 |
106 |
107 | toNiceDate(tick)}
114 | dataKey="dayString"
115 | />
116 | toK(tick)}
122 | axisLine={false}
123 | tickLine={false}
124 | interval="preserveEnd"
125 | minTickGap={80}
126 | yAxisId={0}
127 | />
128 | toK(tick)}
134 | axisLine={false}
135 | tickLine={false}
136 | interval="preserveEnd"
137 | minTickGap={80}
138 | yAxisId={1}
139 | />
140 | toK(val, true)}
143 | labelFormatter={(label) => toNiceDateYear(label)}
144 | labelStyle={{ paddingTop: 4 }}
145 | contentStyle={{
146 | padding: '10px 14px',
147 | borderRadius: 10,
148 | borderColor: 'var(--c-zircon)',
149 | }}
150 | wrapperStyle={{ top: -70, left: -10 }}
151 | />
152 |
163 |
171 |
180 |
181 |
182 |
183 | )
184 | } else {
185 | // volume
186 | return (
187 |
188 |
189 |
190 |
191 | toNiceDate(tick)}
198 | dataKey="dayString"
199 | />
200 | toK(tick)}
206 | tickLine={false}
207 | interval="preserveEnd"
208 | minTickGap={80}
209 | yAxisId={0}
210 | />
211 | toK(val, true)}
214 | labelFormatter={(label) => toNiceDateYear(label)}
215 | labelStyle={{ paddingTop: 4 }}
216 | contentStyle={{
217 | padding: '10px 14px',
218 | borderRadius: 10,
219 | borderColor: 'var(--c-zircon)',
220 | }}
221 | wrapperStyle={{ top: -70, left: -10 }}
222 | />
223 |
232 |
233 |
234 |
235 | )
236 | }
237 | }
238 |
239 | export default Chart
240 |
--------------------------------------------------------------------------------
/src/components/Checkbox/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import { TYPE } from '../../Theme'
4 | import { RowFixed } from '../Row'
5 |
6 | const StyleCheckbox = styled.input`
7 | background: ${({ theme }) => theme.bg2};
8 |
9 | :before {
10 | background: #f35429;
11 | }
12 |
13 | :hover {
14 | cursor: pointer;
15 | }
16 | `
17 |
18 | const ButtonText = styled(TYPE.main)`
19 | cursor: pointer;
20 | :hover {
21 | opacity: 0.6;
22 | }
23 | `
24 |
25 | const CheckBox = ({ checked, setChecked, text }) => {
26 | return (
27 |
28 |
29 |
30 | {text}
31 |
32 |
33 | )
34 | }
35 |
36 | export default CheckBox
37 |
--------------------------------------------------------------------------------
/src/components/Column/index.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | const Column = styled.div`
4 | display: flex;
5 | flex-direction: column;
6 | justify-content: flex-start;
7 | `
8 | export const ColumnCenter = styled(Column)`
9 | width: 100%;
10 | align-items: center;
11 | `
12 |
13 | export const AutoColumn = styled.div`
14 | display: grid;
15 | grid-auto-rows: auto;
16 | grid-row-gap: ${({ gap }) => (gap === 'sm' && '8px') || (gap === 'md' && '12px') || (gap === 'lg' && '24px') || gap};
17 | justify-items: ${({ justify }) => justify && justify};
18 | `
19 |
20 | export default Column
21 |
--------------------------------------------------------------------------------
/src/components/Copy/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import { useCopyClipboard } from '../../hooks'
4 | import { CheckCircle, Copy } from 'react-feather'
5 | import { StyledIcon } from '..'
6 |
7 | const CopyIcon = styled.div`
8 | color: #aeaeae;
9 | flex-shrink: 0;
10 | margin-right: 1rem;
11 | margin-left: 0.5rem;
12 | text-decoration: none;
13 | :hover,
14 | :active,
15 | :focus {
16 | text-decoration: none;
17 | opacity: 0.8;
18 | cursor: pointer;
19 | }
20 | `
21 | const TransactionStatusText = styled.span`
22 | margin-left: 0.25rem;
23 | ${({ theme }) => theme.flexRowNoWrap};
24 | align-items: center;
25 | color: black;
26 | `
27 |
28 | export default function CopyHelper({ toCopy }) {
29 | const [isCopied, setCopied] = useCopyClipboard()
30 |
31 | return (
32 | setCopied(toCopy)}>
33 | {isCopied ? (
34 |
35 |
36 |
37 |
38 |
39 | ) : (
40 |
41 |
42 |
43 |
44 |
45 | )}
46 |
47 | )
48 | }
49 |
--------------------------------------------------------------------------------
/src/components/CurrencySelect/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import styled from 'styled-components'
3 |
4 | import { useCurrentCurrency } from '../../contexts/Application'
5 |
6 | import Row from '../Row'
7 | import { ChevronDown as Arrow } from 'react-feather'
8 |
9 | const Select = styled.div`
10 | position: relative;
11 | display: flex;
12 | flex-direction: column;
13 | align-items: center;
14 | justify-content: center;
15 |
16 | width: fit-content;
17 | height: 38px;
18 | border-radius: 20px;
19 | font-weight: 500;
20 | font-size: 1rem;
21 | color: ${({ theme }) => theme.textColor};
22 |
23 | :hover {
24 | cursor: pointer;
25 | }
26 |
27 | @media screen and (max-width: 40em) {
28 | display: none;
29 | }
30 | `
31 |
32 | const ArrowStyled = styled(Arrow)`
33 | height: 20px;
34 | width: 20px;
35 | margin-left: 6px;
36 | `
37 |
38 | const Option = styled(Row)`
39 | position: absolute;
40 | top: 40px;
41 | `
42 |
43 | const CurrencySelect = () => {
44 | const [showDropdown, toggleDropdown] = useState(false)
45 | const [currency, toggleCurrency] = useCurrentCurrency()
46 |
47 | const getOther = () => {
48 | if (currency === 'USD') {
49 | return 'ETH'
50 | } else {
51 | return 'USD'
52 | }
53 | }
54 |
55 | return (
56 | <>
57 |
72 | >
73 | )
74 | }
75 |
76 | export default CurrencySelect
77 |
--------------------------------------------------------------------------------
/src/components/Dashboard/index.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 | import { Box } from 'rebass'
3 |
4 | const Dashboard = styled(Box)`
5 | width: 100%;
6 | display: grid;
7 | grid-template-columns: 100%;
8 | grid-template-areas:
9 | 'volume'
10 | 'liquidity'
11 | 'shares'
12 | 'statistics'
13 | 'exchange'
14 | 'transactions';
15 |
16 | @media screen and (min-width: 64em) {
17 | max-width: 1320px;
18 | grid-gap: 24px;
19 | width: 100%;
20 | grid-template-columns: 1fr 1fr 1fr;
21 | grid-template-areas:
22 | /* "statsHeader statsHeader statsHeader" */
23 | 'fill fill fill'
24 | 'pairHeader pairHeader pairHeader'
25 | 'transactions2 transactions2 transactions2'
26 | 'listOptions listOptions listOptions'
27 | 'transactions transactions transactions';
28 | }
29 | `
30 |
31 | export default Dashboard
32 |
--------------------------------------------------------------------------------
/src/components/DoubleLogo/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import TokenLogo from '../TokenLogo'
4 |
5 | export default function DoubleTokenLogo({ a0, a1, size = 24, margin = false }) {
6 | const TokenWrapper = styled.div`
7 | position: relative;
8 | display: flex;
9 | flex-direction: row;
10 | margin-right: ${({ sizeraw, margin }) => margin && (sizeraw / 3 + 8).toString() + 'px'};
11 | `
12 |
13 | const HigherLogo = styled(TokenLogo)`
14 | z-index: 2;
15 | // background-color: white;
16 | border-radius: 50%;
17 | `
18 |
19 | const CoveredLogo = styled(TokenLogo)`
20 | position: absolute;
21 | left: ${({ sizeraw }) => (sizeraw / 2).toString() + 'px'};
22 | // background-color: white;
23 | border-radius: 50%;
24 | `
25 |
26 | return (
27 |
28 |
29 |
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/src/components/DropdownSelect/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import styled from 'styled-components'
3 |
4 | import Row, { RowBetween } from '../Row'
5 | import { AutoColumn } from '../Column'
6 | import { ChevronDown as Arrow } from 'react-feather'
7 | import { TYPE } from '../../Theme'
8 | import { StyledIcon } from '..'
9 |
10 | const Wrapper = styled.div`
11 | z-index: 20;
12 | position: relative;
13 | background-color: ${({ theme }) => theme.panelColor};
14 | border: 1px solid ${({ open, color }) => (open ? color : 'rgba(0, 0, 0, 0.15);')}
15 | width: 100px;
16 | padding: 4px 10px;
17 | padding-right: 6px;
18 | border-radius: 8px;
19 | display: flex;
20 | align-items: center;
21 | justify-content: center;
22 |
23 | :hover {
24 | cursor: pointer;
25 | }
26 | `
27 |
28 | const Dropdown = styled.div`
29 | position: absolute;
30 | top: 34px;
31 | padding-top: 40px;
32 | width: calc(100% - 40px);
33 | background-color: ${({ theme }) => theme.bg1};
34 | border: 1px solid rgba(0, 0, 0, 0.15);
35 | padding: 10px 10px;
36 | border-radius: 8px;
37 | width: calc(100% - 20px);
38 | font-weight: 500;
39 | font-size: 1rem;
40 | color: black;
41 | :hover {
42 | cursor: pointer;
43 | }
44 | `
45 |
46 | const ArrowStyled = styled(Arrow)`
47 | height: 20px;
48 | width: 20px;
49 | margin-left: 6px;
50 | `
51 |
52 | const DropdownSelect = ({ options, active, setActive, color }) => {
53 | const [showDropdown, toggleDropdown] = useState(false)
54 |
55 | return (
56 |
57 | toggleDropdown(!showDropdown)} justify="center">
58 | {active}
59 |
60 |
61 |
62 |
63 | {showDropdown && (
64 |
65 |
66 | {Object.keys(options).map((key, index) => {
67 | let option = options[key]
68 | return (
69 | option !== active && (
70 | {
72 | toggleDropdown(!showDropdown)
73 | setActive(option)
74 | }}
75 | key={index}
76 | >
77 | {option}
78 |
79 | )
80 | )
81 | })}
82 |
83 |
84 | )}
85 |
86 | )
87 | }
88 |
89 | export default DropdownSelect
90 |
--------------------------------------------------------------------------------
/src/components/Emoji/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Emoji = (props) => (
4 |
10 | {props.symbol}
11 |
12 | )
13 |
14 | export default Emoji
15 |
--------------------------------------------------------------------------------
/src/components/Footer/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Flex } from 'rebass'
3 |
4 | import Link from '../Link'
5 |
6 | const links = [
7 | { url: 'https://uniswap.io', text: 'About' },
8 | { url: 'https://docs.uniswap.io/', text: 'Docs' },
9 | { url: 'https://github.com/Uniswap/uniswap-info', text: 'Code' },
10 | ]
11 |
12 | const FooterLink = ({ children, ...rest }) => (
13 |
14 | {children}
15 |
16 | )
17 |
18 | const Footer = () => (
19 |
20 | {links.map((link, index) => (
21 |
22 | {link.text}
23 |
24 | ))}
25 |
26 | )
27 |
28 | export default Footer
29 |
--------------------------------------------------------------------------------
/src/components/FormattedName/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import styled from 'styled-components'
3 | import { Tooltip } from '../QuestionHelper'
4 |
5 | const TextWrapper = styled.div`
6 | position: relative;
7 | margin-left: ${({ margin }) => margin && '4px'};
8 | color: ${({ theme, link }) => (link ? theme.blue : theme.text1)};
9 | font-size: ${({ fontSize }) => fontSize ?? 'inherit'};
10 |
11 | :hover {
12 | cursor: pointer;
13 | }
14 |
15 | @media screen and (max-width: 600px) {
16 | font-size: ${({ adjustSize }) => adjustSize && '12px'};
17 | }
18 | `
19 |
20 | const FormattedName = ({ text, maxCharacters, margin = false, adjustSize = false, fontSize, link, ...rest }) => {
21 | const [showHover, setShowHover] = useState(false)
22 |
23 | if (!text) {
24 | return ''
25 | }
26 |
27 | if (text.length > maxCharacters) {
28 | return (
29 |
30 | setShowHover(true)}
32 | onMouseLeave={() => setShowHover(false)}
33 | margin={margin}
34 | adjustSize={adjustSize}
35 | link={link}
36 | fontSize={fontSize}
37 | {...rest}
38 | >
39 | {' ' + text.slice(0, maxCharacters - 1) + '...'}
40 |
41 |
42 | )
43 | }
44 |
45 | return (
46 |
47 | {text}
48 |
49 | )
50 | }
51 |
52 | export default FormattedName
53 |
--------------------------------------------------------------------------------
/src/components/GlobalChart/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useMemo, useEffect, useRef } from 'react'
2 | import { ResponsiveContainer } from 'recharts'
3 | import { timeframeOptions } from '../../constants'
4 | import { useGlobalChartData, useGlobalData } from '../../contexts/GlobalData'
5 | import { useMedia } from 'react-use'
6 | import DropdownSelect from '../DropdownSelect'
7 | import TradingViewChart, { CHART_TYPES } from '../TradingviewChart'
8 | import { RowFixed } from '../Row'
9 | import { OptionButton } from '../ButtonStyled'
10 | import { getTimeframe } from '../../utils'
11 | import { TYPE } from '../../Theme'
12 |
13 | const CHART_VIEW = {
14 | VOLUME: 'Volume',
15 | LIQUIDITY: 'Liquidity',
16 | }
17 |
18 | const VOLUME_WINDOW = {
19 | WEEKLY: 'WEEKLY',
20 | DAYS: 'DAYS',
21 | }
22 | const GlobalChart = ({ display }) => {
23 | // chart options
24 | const [chartView, setChartView] = useState(display === 'volume' ? CHART_VIEW.VOLUME : CHART_VIEW.LIQUIDITY)
25 |
26 | // time window and window size for chart
27 | const timeWindow = timeframeOptions.ALL_TIME
28 | const [volumeWindow, setVolumeWindow] = useState(VOLUME_WINDOW.DAYS)
29 |
30 | // global historical data
31 | const [dailyData, weeklyData] = useGlobalChartData()
32 | const { totalLiquidityUSD, oneDayVolumeUSD, volumeChangeUSD, liquidityChangeUSD, oneWeekVolume, weeklyVolumeChange } =
33 | useGlobalData()
34 |
35 | // based on window, get starttim
36 | let utcStartTime = getTimeframe(timeWindow)
37 |
38 | const chartDataFiltered = useMemo(() => {
39 | let currentData = volumeWindow === VOLUME_WINDOW.DAYS ? dailyData : weeklyData
40 | return (
41 | currentData &&
42 | Object.keys(currentData)
43 | ?.map((key) => {
44 | let item = currentData[key]
45 | if (item.date > utcStartTime) {
46 | return item
47 | } else {
48 | return true
49 | }
50 | })
51 | .filter((item) => {
52 | return !!item
53 | })
54 | )
55 | }, [dailyData, utcStartTime, volumeWindow, weeklyData])
56 | const below800 = useMedia('(max-width: 800px)')
57 |
58 | // update the width on a window resize
59 | const ref = useRef()
60 | const isClient = typeof window === 'object'
61 | const [width, setWidth] = useState(ref?.current?.container?.clientWidth)
62 | useEffect(() => {
63 | if (!isClient) {
64 | return false
65 | }
66 | function handleResize() {
67 | setWidth(ref?.current?.container?.clientWidth ?? width)
68 | }
69 | window.addEventListener('resize', handleResize)
70 | return () => window.removeEventListener('resize', handleResize)
71 | }, [isClient, width]) // Empty array ensures that effect is only run on mount and unmount
72 |
73 | return chartDataFiltered ? (
74 | <>
75 | {below800 && (
76 |
77 | )}
78 |
79 | {chartDataFiltered && chartView === CHART_VIEW.LIQUIDITY && (
80 |
81 |
90 |
91 | )}
92 | {chartDataFiltered && chartView === CHART_VIEW.VOLUME && (
93 |
94 |
104 |
105 | )}
106 | {display === 'volume' && (
107 |
115 | setVolumeWindow(VOLUME_WINDOW.DAYS)}
118 | >
119 | D
120 |
121 | setVolumeWindow(VOLUME_WINDOW.WEEKLY)}
125 | >
126 | W
127 |
128 |
129 | )}
130 | >
131 | ) : (
132 | ''
133 | )
134 | }
135 |
136 | export default GlobalChart
137 |
--------------------------------------------------------------------------------
/src/components/GlobalStats/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import styled from 'styled-components'
3 | import { RowFixed, RowBetween } from '../Row'
4 | import { useMedia } from 'react-use'
5 | import { useGlobalData, useEthPrice } from '../../contexts/GlobalData'
6 | import { formattedNum, localNumber } from '../../utils'
7 |
8 | import UniPrice from '../UniPrice'
9 | import { TYPE } from '../../Theme'
10 |
11 | const Header = styled.div`
12 | width: 100%;
13 | position: sticky;
14 | top: 0;
15 | `
16 |
17 | const Medium = styled.span`
18 | font-weight: 500;
19 | `
20 |
21 | export default function GlobalStats() {
22 | const below1295 = useMedia('(max-width: 1295px)')
23 | const below1180 = useMedia('(max-width: 1180px)')
24 | const below1024 = useMedia('(max-width: 1024px)')
25 | const below400 = useMedia('(max-width: 400px)')
26 | const below816 = useMedia('(max-width: 816px)')
27 |
28 | const [showPriceCard, setShowPriceCard] = useState(false)
29 |
30 | const { oneDayVolumeUSD, oneDayTxns, pairCount } = useGlobalData()
31 | const [ethPrice] = useEthPrice()
32 | const formattedEthPrice = ethPrice ? formattedNum(ethPrice, true) : '-'
33 | const oneDayFees = oneDayVolumeUSD ? formattedNum(oneDayVolumeUSD * 0.003, true) : ''
34 |
35 | return (
36 |
37 |
38 |
39 | {!below400 && (
40 | {
43 | setShowPriceCard(true)
44 | }}
45 | onMouseLeave={() => {
46 | setShowPriceCard(false)
47 | }}
48 | style={{ position: 'relative' }}
49 | >
50 | ETH Price: {formattedEthPrice}
51 | {showPriceCard && }
52 |
53 | )}
54 |
55 | {!below1180 && (
56 |
57 | Transactions (24H): {localNumber(oneDayTxns)}
58 |
59 | )}
60 | {!below1024 && (
61 |
62 | Pairs: {localNumber(pairCount)}
63 |
64 | )}
65 | {!below1295 && (
66 |
67 | Fees (24H): {oneDayFees}
68 |
69 | )}
70 |
71 |
72 |
73 | )
74 | }
75 |
--------------------------------------------------------------------------------
/src/components/HoverText/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useState } from 'react'
2 | import styled from 'styled-components'
3 | import Popover, { PopoverProps } from '../Popover'
4 |
5 | const Wrapper = styled.span`
6 | display: flex;
7 | justify-content: center;
8 | align-items: center;
9 | `
10 |
11 | const TooltipContainer = styled.div`
12 | width: 228px;
13 | padding: 0.6rem 1rem;
14 | line-height: 150%;
15 | font-weight: 400;
16 | `
17 |
18 | interface TooltipProps extends Omit {
19 | text: string
20 | }
21 |
22 | export function Tooltip({ text, ...rest }: TooltipProps) {
23 | return {text}} {...rest} />
24 | }
25 |
26 | export default function HoverText({ text, children }: { text: string; children: any }) {
27 | const [show, setShow] = useState(false)
28 | const open = useCallback(() => setShow(true), [setShow])
29 | const close = useCallback(() => setShow(false), [setShow])
30 |
31 | return (
32 |
33 |
34 |
35 | {children}
36 |
37 |
38 |
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/src/components/LPList/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 | import { useMedia } from 'react-use'
3 | import dayjs from 'dayjs'
4 | import LocalLoader from '../LocalLoader'
5 | import utc from 'dayjs/plugin/utc'
6 | import { Box, Flex } from 'rebass'
7 | import styled from 'styled-components'
8 |
9 | import { CustomLink } from '../Link'
10 | import { Divider } from '..'
11 | import { withRouter } from 'react-router-dom'
12 | import { formattedNum } from '../../utils'
13 | import { TYPE } from '../../Theme'
14 | import DoubleTokenLogo from '../DoubleLogo'
15 | import { RowFixed } from '../Row'
16 |
17 | dayjs.extend(utc)
18 |
19 | const PageButtons = styled.div`
20 | width: 100%;
21 | display: flex;
22 | justify-content: center;
23 | margin-top: 2em;
24 | margin-bottom: 0.5em;
25 | `
26 |
27 | const Arrow = styled.div`
28 | color: ${({ theme }) => theme.primary1};
29 | opacity: ${(props) => (props.faded ? 0.3 : 1)};
30 | padding: 0 20px;
31 | user-select: none;
32 | :hover {
33 | cursor: pointer;
34 | }
35 | `
36 |
37 | const List = styled(Box)`
38 | -webkit-overflow-scrolling: touch;
39 | `
40 |
41 | const DashGrid = styled.div`
42 | display: grid;
43 | grid-gap: 1em;
44 | grid-template-columns: 10px 1.5fr 1fr 1fr;
45 | grid-template-areas: 'number name pair value';
46 | padding: 0 4px;
47 |
48 | > * {
49 | justify-content: flex-end;
50 | }
51 |
52 | @media screen and (max-width: 1080px) {
53 | grid-template-columns: 10px 1.5fr 1fr 1fr;
54 | grid-template-areas: 'number name pair value';
55 | }
56 |
57 | @media screen and (max-width: 600px) {
58 | grid-template-columns: 1fr 1fr 1fr;
59 | grid-template-areas: 'name pair value';
60 | }
61 | `
62 |
63 | const ListWrapper = styled.div``
64 |
65 | const DataText = styled(Flex)`
66 | align-items: center;
67 | text-align: center;
68 | color: ${({ theme }) => theme.text1};
69 | & > * {
70 | font-size: 14px;
71 | }
72 |
73 | @media screen and (max-width: 600px) {
74 | font-size: 13px;
75 | }
76 | `
77 |
78 | function LPList({ lps, disbaleLinks, maxItems = 10 }) {
79 | const below600 = useMedia('(max-width: 600px)')
80 | const below800 = useMedia('(max-width: 800px)')
81 |
82 | // pagination
83 | const [page, setPage] = useState(1)
84 | const [maxPage, setMaxPage] = useState(1)
85 | const ITEMS_PER_PAGE = maxItems
86 |
87 | useEffect(() => {
88 | setMaxPage(1) // edit this to do modular
89 | setPage(1)
90 | }, [lps])
91 |
92 | useEffect(() => {
93 | if (lps) {
94 | let extraPages = 1
95 | if (Object.keys(lps).length % ITEMS_PER_PAGE === 0) {
96 | extraPages = 0
97 | }
98 | setMaxPage(Math.floor(Object.keys(lps).length / ITEMS_PER_PAGE) + extraPages)
99 | }
100 | }, [ITEMS_PER_PAGE, lps])
101 |
102 | const ListItem = ({ lp, index }) => {
103 | return (
104 |
105 | {!below600 && (
106 |
107 | {index}
108 |
109 | )}
110 |
111 |
112 | {below800 ? lp.user.id.slice(0, 4) + '...' + lp.user.id.slice(38, 42) : lp.user.id}
113 |
114 |
115 |
116 | {/* {!below1080 && (
117 |
118 | {lp.type}
119 |
120 | )} */}
121 |
122 |
123 |
124 |
125 | {!below600 && }
126 | {lp.pairName}
127 |
128 |
129 |
130 | {formattedNum(lp.usd, true)}
131 |
132 | )
133 | }
134 |
135 | const lpList =
136 | lps &&
137 | lps.slice(ITEMS_PER_PAGE * (page - 1), page * ITEMS_PER_PAGE).map((lp, index) => {
138 | return (
139 |
143 | )
144 | })
145 |
146 | return (
147 |
148 |
149 | {!below600 && (
150 |
151 | #
152 |
153 | )}
154 |
155 | Account
156 |
157 | {/* {!below1080 && (
158 |
159 | Type
160 |
161 | )} */}
162 |
163 | Pair
164 |
165 |
166 | Value
167 |
168 |
169 |
170 | {!lpList ? : lpList}
171 |
172 | setPage(page === 1 ? page : page - 1)}>
173 |
←
174 |
175 | {'Page ' + page + ' of ' + maxPage}
176 | setPage(page === maxPage ? page : page + 1)}>
177 |
→
178 |
179 |
180 |
181 | )
182 | }
183 |
184 | export default withRouter(LPList)
185 |
--------------------------------------------------------------------------------
/src/components/Link/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link as RebassLink } from 'rebass'
3 | import { Link as RouterLink } from 'react-router-dom'
4 | import PropTypes from 'prop-types'
5 | import styled from 'styled-components'
6 | import { lighten, darken } from 'polished'
7 |
8 | const WrappedLink = ({ external, children, ...rest }) => (
9 |
15 | {children}
16 |
17 | )
18 |
19 | WrappedLink.propTypes = {
20 | external: PropTypes.bool,
21 | }
22 |
23 | const Link = styled(WrappedLink)`
24 | color: ${({ color, theme }) => (color ? color : theme.link)};
25 | `
26 |
27 | export default Link
28 |
29 | export const CustomLink = styled(RouterLink)`
30 | text-decoration: none;
31 | font-size: 14px;
32 | font-weight: 500;
33 | color: ${({ color, theme }) => (color ? color : theme.link)};
34 |
35 | &:visited {
36 | color: ${({ color, theme }) => (color ? lighten(0.1, color) : lighten(0.1, theme.link))};
37 | }
38 |
39 | &:hover {
40 | cursor: pointer;
41 | text-decoration: none;
42 | underline: none;
43 | color: ${({ color, theme }) => (color ? darken(0.1, color) : darken(0.1, theme.link))};
44 | }
45 | `
46 |
47 | export const BasicLink = styled(RouterLink)`
48 | text-decoration: none;
49 | color: inherit;
50 | &:hover {
51 | cursor: pointer;
52 | text-decoration: none;
53 | underline: none;
54 | }
55 | `
56 |
--------------------------------------------------------------------------------
/src/components/LocalLoader/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled, { css, keyframes } from 'styled-components'
3 | import { useDarkModeManager } from '../../contexts/LocalStorage'
4 |
5 | const pulse = keyframes`
6 | 0% { transform: scale(1); }
7 | 60% { transform: scale(1.1); }
8 | 100% { transform: scale(1); }
9 | `
10 |
11 | const Wrapper = styled.div`
12 | pointer-events: none;
13 | display: flex;
14 | align-items: center;
15 | justify-content: center;
16 | height: 100%;
17 | width: 100%;
18 |
19 | ${(props) =>
20 | props.fill && !props.height
21 | ? css`
22 | height: 100vh;
23 | `
24 | : css`
25 | height: 180px;
26 | `}
27 | `
28 |
29 | const AnimatedImg = styled.div`
30 | animation: ${pulse} 800ms linear infinite;
31 | & > * {
32 | width: 72px;
33 | }
34 | `
35 |
36 | const LocalLoader = ({ fill }) => {
37 | const [darkMode] = useDarkModeManager()
38 |
39 | return (
40 |
41 |
42 |
43 |
44 |
45 | )
46 | }
47 |
48 | export default LocalLoader
49 |
--------------------------------------------------------------------------------
/src/components/Meta.tsx:
--------------------------------------------------------------------------------
1 | import { Helmet } from 'react-helmet'
2 | import { withRouter, RouteComponentProps } from 'react-router-dom'
3 | import React from 'react'
4 |
5 | const WEBSITE_HOST_URL = 'https://v2.info.uniswap.org'
6 |
7 | const Meta = ({ location }: RouteComponentProps): JSX.Element => {
8 | return (
9 |
10 |
11 |
12 | )
13 | }
14 |
15 | export default withRouter(Meta)
16 |
--------------------------------------------------------------------------------
/src/components/PairReturnsChart/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import styled from 'styled-components'
3 | import { XAxis, YAxis, ResponsiveContainer, Tooltip, LineChart, Line, CartesianGrid } from 'recharts'
4 | import { AutoRow, RowBetween } from '../Row'
5 |
6 | import { toK, toNiceDate, toNiceDateYear, formattedNum, getTimeframe } from '../../utils'
7 | import { OptionButton } from '../ButtonStyled'
8 | import { useMedia } from 'react-use'
9 | import { timeframeOptions } from '../../constants'
10 | import DropdownSelect from '../DropdownSelect'
11 | import { useUserPositionChart } from '../../contexts/User'
12 | import { useTimeframe } from '../../contexts/Application'
13 | import LocalLoader from '../LocalLoader'
14 | import { useColor } from '../../hooks'
15 | import { useDarkModeManager } from '../../contexts/LocalStorage'
16 |
17 | const ChartWrapper = styled.div`
18 | max-height: 420px;
19 |
20 | @media screen and (max-width: 600px) {
21 | min-height: 200px;
22 | }
23 | `
24 |
25 | const OptionsRow = styled.div`
26 | display: flex;
27 | flex-direction: row;
28 | width: 100%;
29 | margin-bottom: 40px;
30 | `
31 |
32 | const CHART_VIEW = {
33 | VALUE: 'Value',
34 | FEES: 'Fees',
35 | }
36 |
37 | const PairReturnsChart = ({ account, position }) => {
38 | let data = useUserPositionChart(position, account)
39 |
40 | const [timeWindow, setTimeWindow] = useTimeframe()
41 |
42 | const below600 = useMedia('(max-width: 600px)')
43 |
44 | const color = useColor(position?.pair.token0.id)
45 |
46 | const [chartView, setChartView] = useState(CHART_VIEW.VALUE)
47 |
48 | // based on window, get starttime
49 | let utcStartTime = getTimeframe(timeWindow)
50 | data = data?.filter((entry) => entry.date >= utcStartTime)
51 |
52 | const aspect = below600 ? 60 / 42 : 60 / 16
53 |
54 | const [darkMode] = useDarkModeManager()
55 | const textColor = darkMode ? 'white' : 'black'
56 |
57 | return (
58 |
59 | {below600 ? (
60 |
61 |
62 |
63 |
64 | ) : (
65 |
66 |
67 | setChartView(CHART_VIEW.VALUE)}>
68 | Liquidity
69 |
70 | setChartView(CHART_VIEW.FEES)}>
71 | Fees
72 |
73 |
74 |
75 | setTimeWindow(timeframeOptions.WEEK)}
78 | >
79 | 1W
80 |
81 | setTimeWindow(timeframeOptions.MONTH)}
84 | >
85 | 1M
86 |
87 | setTimeWindow(timeframeOptions.ALL_TIME)}
90 | >
91 | All
92 |
93 |
94 |
95 | )}
96 |
97 | {data ? (
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | toNiceDate(tick)}
112 | dataKey="date"
113 | tick={{ fill: textColor }}
114 | type={'number'}
115 | domain={['dataMin', 'dataMax']}
116 | />
117 | '$' + toK(tick)}
121 | axisLine={false}
122 | tickLine={false}
123 | interval="preserveStartEnd"
124 | minTickGap={0}
125 | yAxisId={0}
126 | tick={{ fill: textColor }}
127 | />
128 | formattedNum(val, true)}
131 | labelFormatter={(label) => toNiceDateYear(label)}
132 | labelStyle={{ paddingTop: 4 }}
133 | contentStyle={{
134 | padding: '10px 14px',
135 | borderRadius: 10,
136 | borderColor: color,
137 | color: 'black',
138 | }}
139 | wrapperStyle={{ top: -70, left: -10 }}
140 | />
141 |
142 |
149 |
150 | ) : (
151 |
152 | )}
153 |
154 |
155 | )
156 | }
157 |
158 | export default PairReturnsChart
159 |
--------------------------------------------------------------------------------
/src/components/Panel/index.js:
--------------------------------------------------------------------------------
1 | import { Box as RebassBox } from 'rebass'
2 | import styled, { css } from 'styled-components'
3 |
4 | const panelPseudo = css`
5 | :after {
6 | content: '';
7 | position: absolute;
8 | left: 0;
9 | right: 0;
10 | height: 10px;
11 | }
12 |
13 | @media only screen and (min-width: 40em) {
14 | :after {
15 | content: unset;
16 | }
17 | }
18 | `
19 |
20 | const Panel = styled(RebassBox)`
21 | position: relative;
22 | background-color: ${({ theme }) => theme.advancedBG};
23 | padding: 1.25rem;
24 | width: 100%;
25 | height: 100%;
26 | display: flex;
27 | flex-direction: column;
28 | justify-content: flex-start;
29 | border-radius: 8px;
30 | border: 1px solid ${({ theme }) => theme.bg3};
31 | box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.05); /* box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.01), 0px 16px 24px rgba(0, 0, 0, 0.01), 0px 24px 32px rgba(0, 0, 0, 0.01); */
32 | :hover {
33 | cursor: ${({ hover }) => hover && 'pointer'};
34 | border: ${({ hover, theme }) => hover && '1px solid' + theme.bg5};
35 | }
36 |
37 | ${(props) => props.background && `background-color: ${props.theme.advancedBG};`}
38 |
39 | ${(props) => (props.area ? `grid-area: ${props.area};` : null)}
40 |
41 | ${(props) =>
42 | props.grouped &&
43 | css`
44 | @media only screen and (min-width: 40em) {
45 | &:first-of-type {
46 | border-radius: 20px 20px 0 0;
47 | }
48 | &:last-of-type {
49 | border-radius: 0 0 20px 20px;
50 | }
51 | }
52 | `}
53 |
54 | ${(props) =>
55 | props.rounded &&
56 | css`
57 | border-radius: 8px;
58 | @media only screen and (min-width: 40em) {
59 | border-radius: 10px;
60 | }
61 | `};
62 |
63 | ${(props) => !props.last && panelPseudo}
64 | `
65 |
66 | export default Panel
67 |
68 | // const Panel = styled.div`
69 | // width: 100%;
70 | // height: 100%;
71 | // display: flex;
72 | // flex-direction: column;
73 | // justify-content: flex-start;
74 | // border-radius: 12px;
75 | // background-color: ${({ theme }) => theme.advancedBG};
76 | // padding: 1.25rem;
77 | // box-sizing: border-box;
78 | // box-shadow: 0 1.1px 2.8px -9px rgba(0, 0, 0, 0.008), 0 2.7px 6.7px -9px rgba(0, 0, 0, 0.012),
79 | // 0 5px 12.6px -9px rgba(0, 0, 0, 0.015), 0 8.9px 22.6px -9px rgba(0, 0, 0, 0.018),
80 | // 0 16.7px 42.2px -9px rgba(0, 0, 0, 0.022), 0 40px 101px -9px rgba(0, 0, 0, 0.03);
81 | // `
82 |
--------------------------------------------------------------------------------
/src/components/PinnedData/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { withRouter } from 'react-router-dom'
3 | import styled from 'styled-components'
4 | import { RowBetween, RowFixed } from '../Row'
5 | import { AutoColumn } from '../Column'
6 | import { TYPE } from '../../Theme'
7 | import { useSavedTokens, useSavedPairs } from '../../contexts/LocalStorage'
8 | import { Hover } from '..'
9 | import TokenLogo from '../TokenLogo'
10 | import AccountSearch from '../AccountSearch'
11 | import { Bookmark, ChevronRight, X } from 'react-feather'
12 | import { ButtonFaded } from '../ButtonStyled'
13 | import FormattedName from '../FormattedName'
14 |
15 | const RightColumn = styled.div`
16 | position: fixed;
17 | right: 0;
18 | top: 0px;
19 | height: 100vh;
20 | width: ${({ open }) => (open ? '160px' : '23px')};
21 | padding: 1.25rem;
22 | border-left: ${({ theme, open }) => '1px solid' + theme.bg3};
23 | background-color: ${({ theme }) => theme.bg1};
24 | z-index: 9999;
25 | overflow: auto;
26 | :hover {
27 | cursor: pointer;
28 | }
29 | `
30 |
31 | const SavedButton = styled(RowBetween)`
32 | padding-bottom: ${({ open }) => open && '20px'};
33 | border-bottom: ${({ theme, open }) => open && '1px solid' + theme.bg3};
34 | margin-bottom: ${({ open }) => open && '1.25rem'};
35 |
36 | :hover {
37 | cursor: pointer;
38 | }
39 | `
40 |
41 | const ScrollableDiv = styled(AutoColumn)`
42 | overflow: auto;
43 | padding-bottom: 60px;
44 | `
45 |
46 | const StyledIcon = styled.div`
47 | color: ${({ theme }) => theme.text2};
48 | `
49 |
50 | function PinnedData({ history, open, setSavedOpen }) {
51 | const [savedPairs, , removePair] = useSavedPairs()
52 | const [savedTokens, , removeToken] = useSavedTokens()
53 |
54 | return !open ? (
55 | setSavedOpen(true)}>
56 |
57 |
58 |
59 |
60 |
61 |
62 | ) : (
63 |
64 | setSavedOpen(false)} open={open}>
65 |
66 |
67 |
68 |
69 | Saved
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | Pinned Pairs
79 | {Object.keys(savedPairs).filter((key) => {
80 | return !!savedPairs[key]
81 | }).length > 0 ? (
82 | Object.keys(savedPairs)
83 | .filter((address) => {
84 | return !!savedPairs[address]
85 | })
86 | .map((address) => {
87 | const pair = savedPairs[address]
88 | return (
89 |
90 | history.push('/pair/' + address)}>
91 |
92 |
93 |
98 |
99 |
100 |
101 | removePair(pair.address)}>
102 |
103 |
104 |
105 |
106 |
107 | )
108 | })
109 | ) : (
110 | Pinned pairs will appear here.
111 | )}
112 |
113 |
114 | Pinned Tokens
115 | {Object.keys(savedTokens).filter((key) => {
116 | return !!savedTokens[key]
117 | }).length > 0 ? (
118 | Object.keys(savedTokens)
119 | .filter((address) => {
120 | return !!savedTokens[address]
121 | })
122 | .map((address) => {
123 | const token = savedTokens[address]
124 | return (
125 |
126 | history.push('/token/' + address)}>
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 | removeToken(address)}>
135 |
136 |
137 |
138 |
139 |
140 | )
141 | })
142 | ) : (
143 | Pinned tokens will appear here.
144 | )}
145 |
146 |
147 |
148 | )
149 | }
150 |
151 | export default withRouter(PinnedData)
152 |
--------------------------------------------------------------------------------
/src/components/Popover/index.tsx:
--------------------------------------------------------------------------------
1 | import { Placement } from '@popperjs/core'
2 | import { transparentize } from 'polished'
3 | import React, { useState } from 'react'
4 | import { usePopper } from 'react-popper'
5 | import styled from 'styled-components'
6 | import Portal from '@reach/portal'
7 | import useInterval from '../../hooks'
8 |
9 | const PopoverContainer = styled.div<{ show: boolean }>`
10 | z-index: 9999;
11 |
12 | visibility: ${(props) => (props.show ? 'visible' : 'hidden')};
13 | opacity: ${(props) => (props.show ? 1 : 0)};
14 | transition: visibility 150ms linear, opacity 150ms linear;
15 |
16 | background: ${({ theme }) => theme.bg2};
17 | border: 1px solid ${({ theme }) => theme.bg3};
18 | box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.9, theme.shadow1)};
19 | color: ${({ theme }) => theme.text2};
20 | border-radius: 8px;
21 | `
22 |
23 | const ReferenceElement = styled.div`
24 | display: inline-block;
25 | `
26 |
27 | export interface PopoverProps {
28 | content: React.ReactNode
29 | show: boolean
30 | children: React.ReactNode
31 | placement?: Placement
32 | }
33 |
34 | export default function Popover({ content, show, children, placement = 'auto' }: PopoverProps) {
35 | const [referenceElement, setReferenceElement] = useState(null)
36 | const [popperElement, setPopperElement] = useState(null)
37 | const [arrowElement] = useState(null)
38 | const { styles, update, attributes } = usePopper(referenceElement, popperElement, {
39 | placement,
40 | strategy: 'fixed',
41 | modifiers: [
42 | { name: 'offset', options: { offset: [8, 8] } },
43 | { name: 'arrow', options: { element: arrowElement } },
44 | ],
45 | })
46 |
47 | useInterval(update, show ? 100 : null)
48 |
49 | return (
50 | <>
51 | {children}
52 |
53 |
54 | {content}
55 | {/* */}
61 |
62 |
63 | >
64 | )
65 | }
66 |
--------------------------------------------------------------------------------
/src/components/QuestionHelper/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useState } from 'react'
2 | import { HelpCircle as Question } from 'react-feather'
3 | import styled from 'styled-components'
4 | import Popover, { PopoverProps } from '../Popover'
5 |
6 | const QuestionWrapper = styled.div`
7 | display: flex;
8 | align-items: center;
9 | justify-content: center;
10 | padding: 0.2rem;
11 | border: none;
12 | background: none;
13 | outline: none;
14 | cursor: default;
15 | border-radius: 36px;
16 | background-color: ${({ theme }) => theme.bg2};
17 | color: ${({ theme }) => theme.text2};
18 |
19 | :hover,
20 | :focus {
21 | opacity: 0.7;
22 | }
23 | `
24 |
25 | const TooltipContainer = styled.div`
26 | width: 228px;
27 | padding: 0.6rem 1rem;
28 | line-height: 150%;
29 | font-weight: 400;
30 | `
31 |
32 | interface TooltipProps extends Omit {
33 | text: string
34 | }
35 |
36 | export function Tooltip({ text, ...rest }: TooltipProps) {
37 | return {text}} {...rest} />
38 | }
39 |
40 | export default function QuestionHelper({ text, disabled }: { text: string; disabled?: boolean }) {
41 | const [show, setShow] = useState(false)
42 |
43 | const open = useCallback(() => setShow(true), [setShow])
44 | const close = useCallback(() => setShow(false), [setShow])
45 |
46 | return (
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | )
55 | }
56 |
--------------------------------------------------------------------------------
/src/components/Row/index.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 | import { Box } from 'rebass/styled-components'
3 |
4 | const Row = styled(Box)`
5 | width: 100%;
6 | display: flex;
7 | padding: 0;
8 | align-items: center;
9 | align-items: ${({ align }) => align && align};
10 | padding: ${({ padding }) => padding};
11 | border: ${({ border }) => border};
12 | border-radius: ${({ borderRadius }) => borderRadius};
13 | justify-content: ${({ justify }) => justify};
14 | `
15 |
16 | export const RowBetween = styled(Row)`
17 | justify-content: space-between;
18 | `
19 |
20 | export const RowFlat = styled.div`
21 | display: flex;
22 | align-items: flex-end;
23 | `
24 |
25 | export const AutoRow = styled(Row)`
26 | flex-wrap: ${({ wrap }) => wrap ?? 'nowrap'};
27 | margin: -${({ gap }) => gap};
28 | & > * {
29 | margin: ${({ gap }) => gap} !important;
30 | }
31 | `
32 |
33 | export const RowFixed = styled(Row)`
34 | width: fit-content;
35 | `
36 |
37 | export default Row
38 |
--------------------------------------------------------------------------------
/src/components/Select/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import PropTypes from 'prop-types'
4 | import { default as ReactSelect } from 'react-select'
5 | import { isMobile } from 'react-device-detect'
6 |
7 | import Popout from './popout'
8 |
9 | import { customStyles, customStylesMobile, customStylesTime } from './styles'
10 |
11 | const MenuLabel = styled.div`
12 | display: flex;
13 | align-items: center;
14 | width: 100%;
15 | justify-content: flex-start;
16 | flex-direction: row;
17 | `
18 |
19 | const LabelBox = styled.div``
20 |
21 | const LogoBox = styled.div`
22 | width: 30px;
23 | display: flex;
24 | justify-content: center;
25 | align-items: flex-start;
26 | margin-right: 8px;
27 | `
28 |
29 | const CustomMenu = styled.div`
30 | background-color: white;
31 | position: absolute;
32 | border-radius: 16px;
33 | box-shadow: 0 4px 8px 0 rgba(47, 128, 237, 0.1), 0 0 0 0.5px var(--c-zircon);
34 | overflow: hidden;
35 | padding: 0;
36 | width: 180px;
37 | z-index: 5;
38 | margin-top: 10px;
39 | padding-top: 36px;
40 | `
41 |
42 | const FixedToggle = styled.div`
43 | position: absolute;
44 | height: 24px;
45 | z-index: 10;
46 | background-color: white;
47 | width: 100%;
48 | top: 8px;
49 | display: flex;
50 | align-items: center;
51 | padding-left: 12px;
52 | & > input {
53 | margin-right: 8px;
54 | }
55 | `
56 |
57 | let addressStart = new RegExp('^0x')
58 | function customFilter(option, searchText) {
59 | const isAddress = addressStart.test(searchText)
60 | if (isAddress) {
61 | return option.data.tokenAddress.toString().toLowerCase().includes(searchText.toString().toLowerCase())
62 | }
63 | return option.data.label.toString().toLowerCase().includes(searchText.toString().toLowerCase())
64 | }
65 |
66 | const Select = ({ options, onChange, setCapEth, capEth, tokenSelect = false, placeholder, ...rest }) => {
67 | return tokenSelect ? (
68 | (
76 |
77 | {option.logo}
78 | {option.label}
79 |
80 | )}
81 | styles={isMobile ? customStylesMobile : customStyles}
82 | {...rest}
83 | components={{
84 | DropdownIndicator: () => (
85 |
86 | 🔎
87 |
88 | ),
89 | Menu: ({ children, innerRef, innerProps }) => {
90 | return (
91 |
92 |
93 | {
98 | setCapEth(!capEth)
99 | }}
100 | />
101 | Hide Low Liquidity
102 |
103 | {children}
104 |
105 | )
106 | },
107 | }}
108 | />
109 | ) : (
110 |
118 | )
119 | }
120 |
121 | Select.propTypes = {
122 | options: PropTypes.array.isRequired,
123 | onChange: PropTypes.func,
124 | }
125 |
126 | export default Select
127 |
128 | export { Popout }
129 |
--------------------------------------------------------------------------------
/src/components/Select/popout.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { Button } from 'rebass'
3 | import styled from 'styled-components'
4 |
5 | import Select from 'react-select'
6 |
7 | const selectStyles = {
8 | control: (styles) => ({
9 | ...styles,
10 | padding: '1rem',
11 | border: 'none',
12 | backgroundColor: 'transparent',
13 | borderBottom: '1px solid #e1e1e1',
14 | boxShadow: 'none',
15 | borderRadius: 0,
16 | ':hover': {
17 | borderColor: '#e1e1e1',
18 | },
19 | }),
20 | valueContainer: (styles) => ({
21 | ...styles,
22 | padding: 0,
23 | }),
24 | menu: () => null,
25 | }
26 |
27 | export default class Popout extends Component {
28 | state = { isOpen: false, value: undefined }
29 | toggleOpen = () => {
30 | this.setState((state) => ({ isOpen: !state.isOpen }))
31 | }
32 |
33 | onSelectChange = (value) => {
34 | this.toggleOpen()
35 | this.setState({ value })
36 | }
37 |
38 | render() {
39 | const { isOpen, value } = this.state
40 | return (
41 |
56 | {value ? value.label : 'Select...'}
57 |
58 | }
59 | >
60 |
75 |
76 | )
77 | }
78 | }
79 |
80 | // styled components
81 | const Wrapper = styled.div`
82 | @media only screen and (min-width: 768px) {
83 | max-width: 560px;
84 | max-height: 768px;
85 | position: absolute;
86 | margin-left: auto;
87 | margin-right: auto;
88 | border-radius: 1rem;
89 | padding-bottom: 1rem;
90 | left: 0;
91 | right: 0;
92 | bottom: 0;
93 | top: 0;
94 | margin-top: 4rem;
95 | }
96 |
97 | background-color: #fff;
98 | position: relative;
99 | bottom: 21rem;
100 | width: 100%;
101 | height: 21rem;
102 | z-index: 2000;
103 | border-top-left-radius: 1rem;
104 | border-top-right-radius: 1rem;
105 | transition: 0.25s ease-in-out;
106 | `
107 |
108 | const Menu = (props) =>
109 |
110 | const Blanket = (props) => (
111 |
123 | )
124 |
125 | const Dropdown = ({ children, isOpen, target, onClose }) => (
126 | <>
127 | {target}
128 | {isOpen ? : null}
129 | {isOpen ? : null}
130 | >
131 | )
132 |
133 | const DropdownIndicator = () => (
134 |
135 | 🔎
136 |
137 | )
138 |
--------------------------------------------------------------------------------
/src/components/Select/styles.js:
--------------------------------------------------------------------------------
1 | import theme from '../Theme/theme'
2 | const color = theme.colors
3 |
4 | export const customStyles = {
5 | control: (styles, state) => ({
6 | ...styles,
7 | borderRadius: 20,
8 | backgroundColor: 'white',
9 | color: '#6C7284',
10 | maxHeight: '32px',
11 | margin: 0,
12 | padding: 0,
13 | border: 'none',
14 | boxShadow: 'none',
15 | ':hover': {
16 | borderColor: color.zircon,
17 | cursor: 'pointer',
18 | overflow: 'hidden',
19 | },
20 | }),
21 | placeholder: (styles) => ({
22 | ...styles,
23 | color: '#6C7284',
24 | }),
25 | input: (styles) => ({
26 | ...styles,
27 | color: '#6C7284',
28 | overflow: 'hidden',
29 | }),
30 | singleValue: (styles) => ({
31 | ...styles,
32 | color: '#6C7284',
33 | width: '100%',
34 | paddingRight: '8px',
35 | }),
36 | indicatorSeparator: () => ({
37 | display: 'none',
38 | }),
39 | dropdownIndicator: (styles) => ({
40 | ...styles,
41 | color: '#6C7284',
42 | paddingRight: 0,
43 | }),
44 | valueContainer: (styles) => ({
45 | ...styles,
46 | paddingLeft: 16,
47 | textAlign: 'right',
48 | overflow: 'scroll',
49 | }),
50 | menuPlacer: (styles) => ({
51 | ...styles,
52 | }),
53 | option: (styles, state) => ({
54 | ...styles,
55 | margin: '0px 0px',
56 | padding: 'calc(12px - 1px) calc(12px - 1px)',
57 | width: '',
58 | lineHeight: 1,
59 | color: state.isSelected ? '#000' : '',
60 | border: state.isSelected ? '1px solid var(--c-zircon)' : '1px solid transparent',
61 | borderRadius: state.isSelected && 30,
62 | backgroundColor: state.isSelected ? 'var(--c-alabaster)' : '',
63 | ':hover': {
64 | backgroundColor: 'var(--c-alabaster)',
65 | cursor: 'pointer',
66 | },
67 | }),
68 | menu: (styles) => ({
69 | ...styles,
70 | borderRadius: 16,
71 | boxShadow: '0 4px 8px 0 rgba(47, 128, 237, 0.1), 0 0 0 0.5px var(--c-zircon)',
72 | overflow: 'hidden',
73 | padding: 0,
74 | }),
75 | menuList: (styles) => ({
76 | ...styles,
77 | color: color.text,
78 | padding: 0,
79 | }),
80 | }
81 |
82 | export const customStylesMobile = {
83 | control: (styles, state) => ({
84 | ...styles,
85 | borderRadius: 12,
86 | backgroundColor: 'white',
87 | color: '#6C7284',
88 | maxHeight: '32px',
89 | margin: 0,
90 | padding: 0,
91 | boxShadow: 'none',
92 | ':hover': {
93 | borderColor: color.zircon,
94 | cursor: 'pointer',
95 | },
96 | }),
97 | placeholder: (styles) => ({
98 | ...styles,
99 | color: '#6C7284',
100 | }),
101 | input: (styles) => ({
102 | ...styles,
103 | color: '6C7284',
104 | overflow: 'hidden',
105 | }),
106 | singleValue: (styles) => ({
107 | ...styles,
108 | color: '#6C7284',
109 | }),
110 | indicatorSeparator: () => ({
111 | display: 'none',
112 | }),
113 | dropdownIndicator: (styles) => ({
114 | ...styles,
115 | paddingRight: 0,
116 | }),
117 | valueContainer: (styles) => ({
118 | ...styles,
119 | paddingLeft: 16,
120 | }),
121 | menuPlacer: (styles) => ({
122 | ...styles,
123 | }),
124 | option: (styles, state) => ({
125 | ...styles,
126 | margin: '20px 4px',
127 | padding: 'calc(16px - 1px) 16x',
128 | width: '',
129 | lineHeight: 1,
130 | color: state.isSelected ? '#000' : '',
131 | // border: state.isSelected ? '1px solid var(--c-zircon)' : '1px solid transparent',
132 | borderRadius: state.isSelected && 30,
133 | backgroundColor: state.isSelected ? 'var(--c-alabaster)' : '',
134 | ':hover': {
135 | backgroundColor: 'var(--c-alabaster)',
136 | cursor: 'pointer',
137 | },
138 | }),
139 | menu: (styles) => ({
140 | ...styles,
141 | borderRadius: 20,
142 | boxShadow: '0 4px 8px 0 rgba(47, 128, 237, 0.1), 0 0 0 0.5px var(--c-zircon)',
143 | overflow: 'hidden',
144 | paddingBottom: '12px',
145 | }),
146 | menuList: (styles) => ({
147 | ...styles,
148 | color: color.text,
149 | padding: '8px',
150 | }),
151 | }
152 |
153 | export const customStylesTime = {
154 | control: (styles, state) => ({
155 | ...styles,
156 | borderRadius: 20,
157 | backgroundColor: 'white',
158 | color: '#6C7284',
159 | maxHeight: '32px',
160 | margin: 0,
161 | padding: 0,
162 | border: 'none',
163 | boxShadow: 'none',
164 | ':hover': {
165 | borderColor: color.zircon,
166 | cursor: 'pointer',
167 | },
168 | }),
169 | placeholder: (styles) => ({
170 | ...styles,
171 | color: '#6C7284',
172 | }),
173 | input: (styles) => ({
174 | ...styles,
175 | color: 'transparent',
176 | }),
177 | singleValue: (styles) => ({
178 | ...styles,
179 | color: '#6C7284',
180 | width: '100%',
181 | paddingRight: '8px',
182 | }),
183 | indicatorSeparator: () => ({
184 | display: 'none',
185 | }),
186 | dropdownIndicator: (styles) => ({
187 | ...styles,
188 | color: '#6C7284',
189 | paddingRight: 0,
190 | }),
191 | valueContainer: (styles) => ({
192 | ...styles,
193 | paddingLeft: 16,
194 | overflow: 'visible',
195 | textAlign: 'right',
196 | }),
197 | menuPlacer: (styles) => ({
198 | ...styles,
199 | }),
200 | option: (styles, state) => ({
201 | ...styles,
202 | margin: '0px 0px',
203 | padding: 'calc(12px - 1px) calc(24px - 1px)',
204 | width: '',
205 | lineHeight: 1,
206 | color: state.isSelected ? '#000' : '',
207 | border: state.isSelected ? '1px solid var(--c-zircon)' : '1px solid transparent',
208 | borderRadius: state.isSelected && 30,
209 | backgroundColor: state.isSelected ? 'var(--c-alabaster)' : '',
210 | ':hover': {
211 | backgroundColor: 'var(--c-alabaster)',
212 | cursor: 'pointer',
213 | },
214 | }),
215 | menu: (styles) => ({
216 | ...styles,
217 | borderRadius: 16,
218 | boxShadow: '0 4px 8px 0 rgba(47, 128, 237, 0.1), 0 0 0 0.5px var(--c-zircon)',
219 | overflow: 'hidden',
220 | padding: 0,
221 | }),
222 | menuList: (styles) => ({
223 | ...styles,
224 | color: color.text,
225 | padding: 0,
226 | }),
227 | }
228 |
229 | export default customStyles
230 |
--------------------------------------------------------------------------------
/src/components/SideNav/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import { AutoColumn } from '../Column'
4 | import Title from '../Title'
5 | import { BasicLink } from '../Link'
6 | import { useMedia } from 'react-use'
7 | import { transparentize } from 'polished'
8 | import { TYPE } from '../../Theme'
9 | import { withRouter } from 'react-router-dom'
10 | import { TrendingUp, List, PieChart, Disc } from 'react-feather'
11 | import Link from '../Link'
12 | import { useSessionStart } from '../../contexts/Application'
13 | import { useDarkModeManager } from '../../contexts/LocalStorage'
14 | import Toggle from '../Toggle'
15 |
16 | const Wrapper = styled.div`
17 | height: ${({ isMobile }) => (isMobile ? 'initial' : '100vh')};
18 | background-color: ${({ theme }) => transparentize(0.4, theme.bg1)};
19 | color: ${({ theme }) => theme.text1};
20 | padding: 0.5rem 0.5rem 0.5rem 0.75rem;
21 | position: sticky;
22 | top: 0px;
23 | z-index: 9999;
24 | box-sizing: border-box;
25 | /* background-color: #1b1c22; */
26 | background: linear-gradient(193.68deg, #1b1c22 0.68%, #000000 100.48%);
27 | color: ${({ theme }) => theme.bg2};
28 |
29 | @media screen and (max-width: 800px) {
30 | grid-template-columns: 1fr;
31 | position: relative;
32 | }
33 |
34 | @media screen and (max-width: 600px) {
35 | padding: 1rem;
36 | }
37 | `
38 |
39 | const Option = styled.div`
40 | font-weight: 500;
41 | font-size: 14px;
42 | opacity: ${({ activeText }) => (activeText ? 1 : 0.6)};
43 | color: ${({ theme }) => theme.white};
44 | display: flex;
45 | :hover {
46 | opacity: 1;
47 | }
48 | `
49 |
50 | const DesktopWrapper = styled.div`
51 | display: flex;
52 | flex-direction: column;
53 | justify-content: space-between;
54 | height: 100vh;
55 | `
56 |
57 | const MobileWrapper = styled.div`
58 | display: flex;
59 | justify-content: space-between;
60 | align-items: center;
61 | `
62 |
63 | const HeaderText = styled.div`
64 | margin-right: 0.75rem;
65 | font-size: 0.825rem;
66 | font-weight: 500;
67 | display: inline-box;
68 | display: -webkit-inline-box;
69 | opacity: 0.8;
70 | :hover {
71 | opacity: 1;
72 | }
73 | a {
74 | color: ${({ theme }) => theme.white};
75 | }
76 | `
77 |
78 | const Polling = styled.div`
79 | position: fixed;
80 | display: flex;
81 | left: 0;
82 | bottom: 0;
83 | padding: 1rem;
84 | color: white;
85 | opacity: 0.4;
86 | transition: opacity 0.25s ease;
87 | :hover {
88 | opacity: 1;
89 | }
90 | `
91 | const PollingDot = styled.div`
92 | width: 8px;
93 | height: 8px;
94 | min-height: 8px;
95 | min-width: 8px;
96 | margin-right: 0.5rem;
97 | margin-top: 3px;
98 | border-radius: 50%;
99 | background-color: ${({ theme }) => theme.green1};
100 | `
101 |
102 | function SideNav({ history }) {
103 | const below1080 = useMedia('(max-width: 1080px)')
104 |
105 | const below1180 = useMedia('(max-width: 1180px)')
106 |
107 | const seconds = useSessionStart()
108 |
109 | const [isDark, toggleDarkMode] = useDarkModeManager()
110 |
111 | return (
112 |
113 | {!below1080 ? (
114 |
115 |
116 |
117 | {!below1080 && (
118 |
119 |
120 |
124 |
125 |
126 |
136 |
137 |
138 |
148 |
149 |
150 |
151 |
161 |
162 |
163 | )}
164 |
165 |
166 |
167 |
168 | Uniswap.org
169 |
170 |
171 |
172 |
173 | V1 Analytics
174 |
175 |
176 |
177 |
178 | Docs
179 |
180 |
181 |
182 |
183 | Discord
184 |
185 |
186 |
187 |
188 | Twitter
189 |
190 |
191 |
192 |
193 | {!below1180 && (
194 |
195 |
196 |
197 |
198 | Updated {!!seconds ? seconds + 's' : '-'} ago
199 |
200 |
201 |
202 | )}
203 |
204 | ) : (
205 |
206 |
207 |
208 | )}
209 |
210 | )
211 | }
212 |
213 | export default withRouter(SideNav)
214 |
--------------------------------------------------------------------------------
/src/components/Title/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useHistory } from 'react-router-dom'
3 | import styled from 'styled-components'
4 |
5 | import { Flex } from 'rebass'
6 | import Link from '../Link'
7 | import { RowFixed } from '../Row'
8 | import Logo from '../../assets/logo_white.svg'
9 | import Wordmark from '../../assets/wordmark_white.svg'
10 |
11 | import { BasicLink } from '../Link'
12 | import { useMedia } from 'react-use'
13 |
14 | const TitleWrapper = styled.div`
15 | text-decoration: none;
16 | z-index: 10;
17 | width: 100%;
18 | &:hover {
19 | cursor: pointer;
20 | }
21 | `
22 |
23 | const UniIcon = styled(Link)`
24 | transition: transform 0.3s ease;
25 | :hover {
26 | transform: rotate(-5deg);
27 | }
28 | `
29 |
30 | const Option = styled.div`
31 | font-weight: 500;
32 | font-size: 14px;
33 | opacity: ${({ activeText }) => (activeText ? 1 : 0.6)};
34 | color: ${({ theme }) => theme.white};
35 | display: flex;
36 | margin-left: 12px;
37 | :hover {
38 | opacity: 1;
39 | }
40 | `
41 |
42 | export default function Title() {
43 | const history = useHistory()
44 | const below1080 = useMedia('(max-width: 1080px)')
45 |
46 | return (
47 | history.push('/')}>
48 |
49 |
50 | history.push('/')}>
51 |
52 |
53 | {!below1080 && (
54 |
55 | )}
56 |
57 | {below1080 && (
58 |
59 |
60 |
61 |
62 |
63 |
72 |
73 |
74 |
83 |
84 |
85 |
86 |
95 |
96 |
97 | )}
98 |
99 |
100 | )
101 | }
102 |
--------------------------------------------------------------------------------
/src/components/Toggle/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import { Sun, Moon } from 'react-feather'
4 |
5 | const IconWrapper = styled.div<{ isActive?: boolean }>`
6 | opacity: ${({ isActive }) => (isActive ? 0.8 : 0.4)};
7 |
8 | :hover {
9 | opacity: 1;
10 | }
11 | `
12 |
13 | const StyledToggle = styled.div`
14 | display: flex;
15 | width: fit-content;
16 | cursor: pointer;
17 | text-decoration: none;
18 | margin-top: 1rem;
19 | color: white;
20 |
21 | :hover {
22 | text-decoration: none;
23 | }
24 | `
25 |
26 | export interface ToggleProps {
27 | isActive: boolean
28 | toggle: () => void
29 | }
30 |
31 | export default function Toggle({ isActive, toggle }: ToggleProps) {
32 | return (
33 |
34 |
35 |
36 |
37 |
38 |
39 | {' / '}
40 |
41 |
42 |
43 |
44 |
45 |
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/src/components/TokenLogo/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 | import styled from 'styled-components'
3 | import { isAddress } from '../../utils/index.js'
4 | import EthereumLogo from '../../assets/eth.png'
5 |
6 | const BAD_IMAGES = {}
7 |
8 | const Inline = styled.div`
9 | display: flex;
10 | align-items: center;
11 | align-self: center;
12 | `
13 |
14 | const Image = styled.img`
15 | width: ${({ size }) => size};
16 | height: ${({ size }) => size};
17 | background-color: white;
18 | border-radius: 50%;
19 | box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.075);
20 | `
21 |
22 | const StyledEthereumLogo = styled.div`
23 | display: flex;
24 | align-items: center;
25 | justify-content: center;
26 |
27 | > img {
28 | width: ${({ size }) => size};
29 | height: ${({ size }) => size};
30 | }
31 | `
32 |
33 | export default function TokenLogo({ address, header = false, size = '24px', ...rest }) {
34 | const [error, setError] = useState(false)
35 |
36 | useEffect(() => {
37 | setError(false)
38 | }, [address])
39 |
40 | if (error || BAD_IMAGES[address]) {
41 | return (
42 |
43 |
44 | 🤔
45 |
46 |
47 | )
48 | }
49 |
50 | // hard coded fixes for trust wallet api issues
51 | if (address?.toLowerCase() === '0x5e74c9036fb86bd7ecdcb084a0673efc32ea31cb') {
52 | address = '0x42456d7084eacf4083f1140d3229471bba2949a8'
53 | }
54 |
55 | if (address?.toLowerCase() === '0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f') {
56 | address = '0xc011a72400e58ecd99ee497cf89e3775d4bd732f'
57 | }
58 |
59 | if (address?.toLowerCase() === '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2') {
60 | return (
61 |
62 |
70 |
71 | )
72 | }
73 |
74 | const path = `https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/${isAddress(
75 | address
76 | )}/logo.png`
77 |
78 | return (
79 |
80 | {
86 | BAD_IMAGES[address] = true
87 | setError(true)
88 | event.preventDefault()
89 | }}
90 | />
91 |
92 | )
93 | }
94 |
--------------------------------------------------------------------------------
/src/components/TradingviewChart/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react'
2 | import { createChart } from 'lightweight-charts'
3 | import dayjs from 'dayjs'
4 | import utc from 'dayjs/plugin/utc'
5 | import { formattedNum } from '../../utils'
6 | import styled from 'styled-components'
7 | import { usePrevious } from 'react-use'
8 | import { Play } from 'react-feather'
9 | import { useDarkModeManager } from '../../contexts/LocalStorage'
10 | import { IconWrapper } from '..'
11 |
12 | dayjs.extend(utc)
13 |
14 | export const CHART_TYPES = {
15 | BAR: 'BAR',
16 | AREA: 'AREA',
17 | }
18 |
19 | const Wrapper = styled.div`
20 | position: relative;
21 | `
22 |
23 | // constant height for charts
24 | const HEIGHT = 300
25 |
26 | const TradingViewChart = ({
27 | type = CHART_TYPES.BAR,
28 | data,
29 | base,
30 | baseChange,
31 | field,
32 | title,
33 | width,
34 | useWeekly = false,
35 | }) => {
36 | // reference for DOM element to create with chart
37 | const ref = useRef()
38 |
39 | // pointer to the chart object
40 | const [chartCreated, setChartCreated] = useState(false)
41 | const dataPrev = usePrevious(data)
42 |
43 | useEffect(() => {
44 | if (data !== dataPrev && chartCreated && type === CHART_TYPES.BAR) {
45 | // remove the tooltip element
46 | let tooltip = document.getElementById('tooltip-id' + type)
47 | let node = document.getElementById('test-id' + type)
48 | node.removeChild(tooltip)
49 | chartCreated.resize(0, 0)
50 | setChartCreated()
51 | }
52 | }, [chartCreated, data, dataPrev, type])
53 |
54 | // parese the data and format for tardingview consumption
55 | const formattedData = data?.map((entry) => {
56 | return {
57 | time: dayjs.unix(entry.date).utc().format('YYYY-MM-DD'),
58 | value: parseFloat(entry[field]),
59 | }
60 | })
61 |
62 | // adjust the scale based on the type of chart
63 | const topScale = type === CHART_TYPES.AREA ? 0.32 : 0.2
64 |
65 | const [darkMode] = useDarkModeManager()
66 | const textColor = darkMode ? 'white' : 'black'
67 | const previousTheme = usePrevious(darkMode)
68 |
69 | // reset the chart if them switches
70 | useEffect(() => {
71 | if (chartCreated && previousTheme !== darkMode) {
72 | // remove the tooltip element
73 | let tooltip = document.getElementById('tooltip-id' + type)
74 | let node = document.getElementById('test-id' + type)
75 | node.removeChild(tooltip)
76 | chartCreated.resize(0, 0)
77 | setChartCreated()
78 | }
79 | }, [chartCreated, darkMode, previousTheme, type])
80 |
81 | // if no chart created yet, create one with options and add to DOM manually
82 | useEffect(() => {
83 | if (!chartCreated && formattedData) {
84 | var chart = createChart(ref.current, {
85 | width: width,
86 | height: HEIGHT,
87 | layout: {
88 | backgroundColor: 'transparent',
89 | textColor: textColor,
90 | },
91 | rightPriceScale: {
92 | scaleMargins: {
93 | top: topScale,
94 | bottom: 0,
95 | },
96 | borderVisible: false,
97 | },
98 | timeScale: {
99 | borderVisible: false,
100 | },
101 | grid: {
102 | horzLines: {
103 | color: 'rgba(197, 203, 206, 0.5)',
104 | visible: false,
105 | },
106 | vertLines: {
107 | color: 'rgba(197, 203, 206, 0.5)',
108 | visible: false,
109 | },
110 | },
111 | crosshair: {
112 | horzLine: {
113 | visible: false,
114 | labelVisible: false,
115 | },
116 | vertLine: {
117 | visible: true,
118 | style: 0,
119 | width: 2,
120 | color: 'rgba(32, 38, 46, 0.1)',
121 | labelVisible: false,
122 | },
123 | },
124 | localization: {
125 | priceFormatter: (val) => formattedNum(val, true),
126 | },
127 | })
128 |
129 | var series =
130 | type === CHART_TYPES.BAR
131 | ? chart.addHistogramSeries({
132 | color: '#ff007a',
133 | priceFormat: {
134 | type: 'volume',
135 | },
136 | scaleMargins: {
137 | top: 0.32,
138 | bottom: 0,
139 | },
140 | lineColor: '#ff007a',
141 | lineWidth: 3,
142 | })
143 | : chart.addAreaSeries({
144 | topColor: '#ff007a',
145 | bottomColor: 'rgba(255, 0, 122, 0)',
146 | lineColor: '#ff007a',
147 | lineWidth: 3,
148 | })
149 |
150 | series.setData(formattedData)
151 | var toolTip = document.createElement('div')
152 | toolTip.setAttribute('id', 'tooltip-id' + type)
153 | toolTip.className = darkMode ? 'three-line-legend-dark' : 'three-line-legend'
154 | ref.current.appendChild(toolTip)
155 | toolTip.style.display = 'block'
156 | toolTip.style.fontWeight = '500'
157 | toolTip.style.left = -4 + 'px'
158 | toolTip.style.top = '-' + 8 + 'px'
159 | toolTip.style.backgroundColor = 'transparent'
160 |
161 | // format numbers
162 | let percentChange = baseChange?.toFixed(2)
163 | let formattedPercentChange = (percentChange > 0 ? '+' : '') + percentChange + '%'
164 | let color = percentChange >= 0 ? 'green' : 'red'
165 |
166 | // get the title of the chart
167 | function setLastBarText() {
168 | toolTip.innerHTML =
169 | `${title} ${
170 | type === CHART_TYPES.BAR && !useWeekly ? '(24hr)' : ''
171 | }
` +
172 | `` +
173 | formattedNum(base ?? 0, true) +
174 | `${formattedPercentChange}` +
175 | '
'
176 | }
177 | setLastBarText()
178 |
179 | // update the title when hovering on the chart
180 | chart.subscribeCrosshairMove(function (param) {
181 | if (
182 | param === undefined ||
183 | param.time === undefined ||
184 | param.point.x < 0 ||
185 | param.point.x > width ||
186 | param.point.y < 0 ||
187 | param.point.y > HEIGHT
188 | ) {
189 | setLastBarText()
190 | } else {
191 | let dateStr = useWeekly
192 | ? dayjs(param.time.year + '-' + param.time.month + '-' + param.time.day)
193 | .startOf('week')
194 | .format('MMMM D, YYYY') +
195 | '-' +
196 | dayjs(param.time.year + '-' + param.time.month + '-' + param.time.day)
197 | .endOf('week')
198 | .format('MMMM D, YYYY')
199 | : dayjs(param.time.year + '-' + param.time.month + '-' + param.time.day).format('MMMM D, YYYY')
200 | var price = param.seriesPrices.get(series)
201 |
202 | toolTip.innerHTML =
203 | `${title}
` +
204 | `` +
205 | formattedNum(price, true) +
206 | '
' +
207 | '' +
208 | dateStr +
209 | '
'
210 | }
211 | })
212 |
213 | chart.timeScale().fitContent()
214 |
215 | setChartCreated(chart)
216 | }
217 | }, [
218 | base,
219 | baseChange,
220 | chartCreated,
221 | darkMode,
222 | data,
223 | formattedData,
224 | textColor,
225 | title,
226 | topScale,
227 | type,
228 | useWeekly,
229 | width,
230 | ])
231 |
232 | // responsiveness
233 | useEffect(() => {
234 | if (width) {
235 | chartCreated && chartCreated.resize(width, HEIGHT)
236 | chartCreated && chartCreated.timeScale().scrollToPosition(0)
237 | }
238 | }, [chartCreated, width])
239 |
240 | return (
241 |
242 |
243 |
244 | {
246 | chartCreated && chartCreated.timeScale().fitContent()
247 | }}
248 | />
249 |
250 |
251 | )
252 | }
253 |
254 | export default TradingViewChart
255 |
--------------------------------------------------------------------------------
/src/components/UniPrice/index.js:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react'
2 | import styled from 'styled-components'
3 | import Panel from '../Panel'
4 | import { AutoColumn } from '../Column'
5 | import { RowFixed } from '../Row'
6 | import { TYPE } from '../../Theme'
7 | import { usePairData } from '../../contexts/PairData'
8 | import { formattedNum } from '../../utils'
9 |
10 | const PriceCard = styled(Panel)`
11 | position: absolute;
12 | right: -220px;
13 | width: 220px;
14 | top: -20px;
15 | z-index: 9999;
16 | height: fit-content;
17 | background-color: ${({ theme }) => theme.bg1};
18 | `
19 |
20 | function formatPercent(rawPercent) {
21 | if (rawPercent < 0.01) {
22 | return '<1%'
23 | } else return parseFloat(rawPercent * 100).toFixed(0) + '%'
24 | }
25 |
26 | export default function UniPrice() {
27 | const daiPair = usePairData('0xa478c2975ab1ea89e8196811f51a7b7ade33eb11')
28 | const usdcPair = usePairData('0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc')
29 | const usdtPair = usePairData('0x0d4a11d5eeaac28ec3f61d100daf4d40471f1852')
30 |
31 | const totalLiquidity = useMemo(() => {
32 | return daiPair && usdcPair && usdtPair
33 | ? daiPair.trackedReserveUSD + usdcPair.trackedReserveUSD + usdtPair.trackedReserveUSD
34 | : 0
35 | }, [daiPair, usdcPair, usdtPair])
36 |
37 | const daiPerEth = daiPair ? parseFloat(daiPair.token0Price).toFixed(2) : '-'
38 | const usdcPerEth = usdcPair ? parseFloat(usdcPair.token0Price).toFixed(2) : '-'
39 | const usdtPerEth = usdtPair ? parseFloat(usdtPair.token1Price).toFixed(2) : '-'
40 |
41 | return (
42 |
43 |
44 |
45 | DAI/ETH: {formattedNum(daiPerEth, true)}
46 |
47 | {daiPair && totalLiquidity ? formatPercent(daiPair.trackedReserveUSD / totalLiquidity) : '-'}
48 |
49 |
50 |
51 | USDC/ETH: {formattedNum(usdcPerEth, true)}
52 |
53 | {usdcPair && totalLiquidity ? formatPercent(usdcPair.trackedReserveUSD / totalLiquidity) : '-'}
54 |
55 |
56 |
57 | USDT/ETH: {formattedNum(usdtPerEth, true)}
58 |
59 | {usdtPair && totalLiquidity ? formatPercent(usdtPair.trackedReserveUSD / totalLiquidity) : '-'}
60 |
61 |
62 |
63 |
64 | )
65 | }
66 |
--------------------------------------------------------------------------------
/src/components/UserChart/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import styled from 'styled-components'
3 | import { Area, XAxis, YAxis, ResponsiveContainer, Tooltip, AreaChart } from 'recharts'
4 | import { AutoRow, RowBetween } from '../Row'
5 | import { toK, toNiceDate, toNiceDateYear, formattedNum, getTimeframe } from '../../utils'
6 | import { OptionButton } from '../ButtonStyled'
7 | import { darken } from 'polished'
8 | import { useMedia } from 'react-use'
9 | import { timeframeOptions } from '../../constants'
10 | import DropdownSelect from '../DropdownSelect'
11 | import { useUserLiquidityChart } from '../../contexts/User'
12 | import LocalLoader from '../LocalLoader'
13 | import { useDarkModeManager } from '../../contexts/LocalStorage'
14 | import { TYPE } from '../../Theme'
15 |
16 | const ChartWrapper = styled.div`
17 | height: 100%;
18 | max-height: 390px;
19 |
20 | @media screen and (max-width: 600px) {
21 | min-height: 200px;
22 | }
23 | `
24 |
25 | const UserChart = ({ account }) => {
26 | const chartData = useUserLiquidityChart(account)
27 |
28 | const [timeWindow, setTimeWindow] = useState(timeframeOptions.ALL_TIME)
29 | let utcStartTime = getTimeframe(timeWindow)
30 |
31 | const below600 = useMedia('(max-width: 600px)')
32 | const above1600 = useMedia('(min-width: 1600px)')
33 |
34 | const domain = [(dataMin) => (dataMin > utcStartTime ? dataMin : utcStartTime), 'dataMax']
35 |
36 | const aspect = above1600 ? 60 / 12 : below600 ? 60 / 42 : 60 / 16
37 |
38 | const [darkMode] = useDarkModeManager()
39 | const textColor = darkMode ? 'white' : 'black'
40 |
41 | return (
42 |
43 | {below600 ? (
44 |
45 |
46 |
47 |
48 | ) : (
49 |
50 |
51 | Liquidity Value
52 |
53 |
54 | setTimeWindow(timeframeOptions.MONTH)}
57 | >
58 | 1M
59 |
60 | setTimeWindow(timeframeOptions.WEEK)}
63 | >
64 | 1W
65 |
66 | setTimeWindow(timeframeOptions.ALL_TIME)}
69 | >
70 | All
71 |
72 |
73 |
74 | )}
75 | {chartData ? (
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | toNiceDate(tick)}
91 | dataKey="date"
92 | tick={{ fill: textColor }}
93 | type={'number'}
94 | domain={domain}
95 | />
96 | '$' + toK(tick)}
100 | axisLine={false}
101 | tickLine={false}
102 | interval="preserveEnd"
103 | minTickGap={6}
104 | yAxisId={0}
105 | tick={{ fill: textColor }}
106 | />
107 | formattedNum(val, true)}
110 | labelFormatter={(label) => toNiceDateYear(label)}
111 | labelStyle={{ paddingTop: 4 }}
112 | contentStyle={{
113 | padding: '10px 14px',
114 | borderRadius: 10,
115 | borderColor: '#ff007a',
116 | color: 'black',
117 | }}
118 | wrapperStyle={{ top: -70, left: -10 }}
119 | />
120 |
132 |
133 |
134 | ) : (
135 |
136 | )}
137 |
138 | )
139 | }
140 |
141 | export default UserChart
142 |
--------------------------------------------------------------------------------
/src/components/Warning/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import 'feather-icons'
3 | import styled from 'styled-components'
4 | import { Text } from 'rebass'
5 | import { AlertTriangle } from 'react-feather'
6 | import { RowBetween, RowFixed } from '../Row'
7 | import { ButtonDark } from '../ButtonStyled'
8 | import { AutoColumn } from '../Column'
9 | import { Hover } from '..'
10 | import Link from '../Link'
11 | import { useMedia } from 'react-use'
12 |
13 | const WarningWrapper = styled.div`
14 | border-radius: 20px;
15 | border: 1px solid #f82d3a;
16 | background: rgba(248, 45, 58, 0.05);
17 | padding: 1rem;
18 | color: #f82d3a;
19 | display: ${({ show }) => !show && 'none'};
20 | margin: 0 2rem 2rem 2rem;
21 | position: relative;
22 |
23 | @media screen and (max-width: 800px) {
24 | width: 80% !important;
25 | margin-left: 5%;
26 | }
27 | `
28 |
29 | const StyledWarningIcon = styled(AlertTriangle)`
30 | min-height: 20px;
31 | min-width: 20px;
32 | stroke: red;
33 | `
34 |
35 | export default function Warning({ type, show, setShow, address }) {
36 | const below800 = useMedia('(max-width: 800px)')
37 |
38 | const textContent = below800 ? (
39 |
40 |
41 | Anyone can create and name any ERC20 token on Ethereum, including creating fake versions of existing tokens and
42 | tokens that claim to represent projects that do not have a token.
43 |
44 |
45 | Similar to Etherscan, this site automatically tracks analytics for all ERC20 tokens independent of token
46 | integrity. Please do your own research before interacting with any ERC20 token.
47 |
48 |
49 | ) : (
50 |
51 | Anyone can create and name any ERC20 token on Ethereum, including creating fake versions of existing tokens and
52 | tokens that claim to represent projects that do not have a token. Similar to Etherscan, this site automatically
53 | tracks analytics for all ERC20 tokens independent of token integrity. Please do your own research before
54 | interacting with any ERC20 token.
55 |
56 | )
57 |
58 | return (
59 |
60 |
61 |
62 |
63 |
64 | Token Safety Alert
65 |
66 |
67 | {textContent}
68 | {below800 ? (
69 |
70 |
71 |
78 | View {type === 'token' ? 'token' : 'pair'} contract on Etherscan
79 |
80 |
81 |
82 |
83 | setShow(false)}>
84 | I understand
85 |
86 |
87 |
88 | ) : (
89 |
90 |
91 |
98 | View {type === 'token' ? 'token' : 'pair'} contract on Etherscan
99 |
100 |
101 | setShow(false)}>
102 | I understand
103 |
104 |
105 | )}
106 |
107 |
108 | )
109 | }
110 |
--------------------------------------------------------------------------------
/src/components/analytics/GoogleAnalyticsReporter.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react'
2 | import ReactGA from 'react-ga'
3 |
4 | // fires a GA pageview every time the route changes
5 | export default function GoogleAnalyticsReporter({ location: { pathname, search } }) {
6 | useEffect(() => {
7 | ReactGA.pageview(`${pathname}${search}`)
8 | }, [pathname, search])
9 | return null
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import { Text, Box } from 'rebass'
4 |
5 | import Link from './Link'
6 |
7 | import { urls } from '../utils'
8 |
9 | const Divider = styled(Box)`
10 | height: 1px;
11 | background-color: ${({ theme }) => theme.divider};
12 | `
13 |
14 | export const BlockedWrapper = styled.div`
15 | width: 100%;
16 | height: 100%;
17 | display: flex;
18 | justify-content: center;
19 | align-items: center;
20 | `
21 |
22 | export const BlockedMessageWrapper = styled.div`
23 | border: 1px solid ${({ theme }) => theme.text3};
24 | border-radius: 12px;
25 | display: flex;
26 | justify-content: center;
27 | align-items: center;
28 | padding: 1rem;
29 | max-width: 80%;
30 | `
31 |
32 | export const IconWrapper = styled.div`
33 | position: absolute;
34 | right: 0;
35 | border-radius: 3px;
36 | height: 16px;
37 | width: 16px;
38 | padding: 0px;
39 | bottom: 0;
40 | display: flex;
41 | align-items: center;
42 | justify-content: center;
43 | color: ${({ theme }) => theme.text1};
44 |
45 | :hover {
46 | cursor: pointer;
47 | opacity: 0.7;
48 | }
49 | `
50 |
51 | const Hint = ({ children, ...rest }) => (
52 |
53 | {children}
54 |
55 | )
56 |
57 | const Address = ({ address, token, ...rest }) => (
58 |
65 | {address}
66 |
67 | )
68 |
69 | export const Hover = styled.div`
70 | :hover {
71 | cursor: pointer;
72 | opacity: ${({ fade }) => fade && '0.7'};
73 | }
74 | `
75 |
76 | export const StyledIcon = styled.div`
77 | color: ${({ theme }) => theme.text1};
78 | `
79 |
80 | const EmptyCard = styled.div`
81 | display: flex;
82 | align-items: center;
83 | justify-content: center;
84 | height: 200px;
85 | border-radius: 20px;
86 | color: ${({ theme }) => theme.text1};
87 | height: ${({ height }) => height && height};
88 | `
89 |
90 | export const SideBar = styled.span`
91 | display: grid;
92 | grid-gap: 24px;
93 | position: sticky;
94 | top: 4rem;
95 | `
96 |
97 | export const SubNav = styled.ul`
98 | list-style: none;
99 | display: flex;
100 | flex-direction: row;
101 | justify-content: flex-start;
102 | align-items: flex-start;
103 | padding: 0;
104 | margin-bottom: 2rem;
105 | `
106 | export const SubNavEl = styled.li`
107 | list-style: none;
108 | display: flex;
109 | padding-bottom: 0.5rem;
110 | margin-right: 1rem;
111 | font-weight: ${({ isActive }) => (isActive ? 600 : 500)};
112 | border-bottom: 1px solid rgba(0, 0, 0, 0);
113 |
114 | :hover {
115 | cursor: pointer;
116 | border-bottom: 1px solid ${({ theme }) => theme.bg3};
117 | }
118 | `
119 |
120 | export const PageWrapper = styled.div`
121 | display: flex;
122 | flex-direction: column;
123 | padding-top: 36px;
124 | padding-bottom: 80px;
125 |
126 | @media screen and (max-width: 600px) {
127 | & > * {
128 | padding: 0 12px;
129 | }
130 | }
131 | `
132 |
133 | export const ContentWrapper = styled.div`
134 | display: grid;
135 | justify-content: start;
136 | align-items: start;
137 | grid-template-columns: 1fr;
138 | grid-gap: 24px;
139 | max-width: 1440px;
140 | width: 100%;
141 | margin: 0 auto;
142 | padding: 0 2rem;
143 | box-sizing: border-box;
144 | @media screen and (max-width: 1180px) {
145 | grid-template-columns: 1fr;
146 | padding: 0 1rem;
147 | }
148 | `
149 |
150 | export const ContentWrapperLarge = styled.div`
151 | display: grid;
152 | justify-content: start;
153 | align-items: start;
154 | grid-template-columns: 1fr;
155 | grid-gap: 24px;
156 | padding: 0 2rem;
157 | margin: 0 auto;
158 | box-sizing: border-box;
159 | max-width: 1440px;
160 | width: 100%;
161 |
162 | @media screen and (max-width: 1282px) {
163 | grid-template-columns: 1fr;
164 | padding: 0 1rem;
165 | }
166 | `
167 |
168 | export const FullWrapper = styled.div`
169 | display: grid;
170 | justify-content: start;
171 | align-items: start;
172 | grid-template-columns: 1fr;
173 | grid-gap: 24px;
174 | max-width: 1440px;
175 | width: 100%;
176 | margin: 0 auto;
177 | padding: 0 2rem;
178 | box-sizing: border-box;
179 |
180 | @media screen and (max-width: 1180px) {
181 | grid-template-columns: 1fr;
182 | padding: 0 1rem;
183 | }
184 | `
185 |
186 | export const FixedMenu = styled.div`
187 | z-index: 99;
188 | width: 100%;
189 | box-sizing: border-box;
190 | padding: 1rem;
191 | box-sizing: border-box;
192 | margin-bottom: 2rem;
193 | max-width: 100vw;
194 |
195 | @media screen and (max-width: 800px) {
196 | margin-bottom: 0;
197 | }
198 | `
199 |
200 | export { Hint, Divider, Address, EmptyCard }
201 |
--------------------------------------------------------------------------------
/src/constants/index.js:
--------------------------------------------------------------------------------
1 | export const FACTORY_ADDRESS = '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f'
2 |
3 | export const BUNDLE_ID = '1'
4 |
5 | export const timeframeOptions = {
6 | WEEK: '1 week',
7 | MONTH: '1 month',
8 | // THREE_MONTHS: '3 months',
9 | // YEAR: '1 year',
10 | HALF_YEAR: '6 months',
11 | ALL_TIME: 'All time',
12 | }
13 |
14 | // token list urls to fetch tokens from - use for warnings on tokens and pairs
15 | export const SUPPORTED_LIST_URLS__NO_ENS = [
16 | 'https://gateway.ipfs.io/ipns/tokens.uniswap.org',
17 | 'https://www.coingecko.com/tokens_list/uniswap/defi_100/v_0_0_0.json',
18 | ]
19 |
20 | // hide from overview list
21 | export const TOKEN_BLACKLIST = [
22 | '0x495c7f3a713870f68f8b418b355c085dfdc412c3',
23 | '0xc3761eb917cd790b30dad99f6cc5b4ff93c4f9ea',
24 | '0xe31debd7abff90b06bca21010dd860d8701fd901',
25 | '0xfc989fbb6b3024de5ca0144dc23c18a063942ac1',
26 | '0xf4eda77f0b455a12f3eb44f8653835f377e36b76',
27 | '0x93b2fff814fcaeffb01406e80b4ecd89ca6a021b',
28 |
29 | // rebass tokens
30 | '0x9ea3b5b4ec044b70375236a281986106457b20ef',
31 | '0x05934eba98486693aaec2d00b0e9ce918e37dc3f',
32 | '0x3d7e683fc9c86b4d653c9e47ca12517440fad14e',
33 | '0xfae9c647ad7d89e738aba720acf09af93dc535f7',
34 | '0x7296368fe9bcb25d3ecc19af13655b907818cc09',
35 | ]
36 |
37 | // pair blacklist
38 | export const PAIR_BLACKLIST = [
39 | '0xb6a741f37d6e455ebcc9f17e2c16d0586c3f57a5',
40 | '0x97cb8cbe91227ba87fc21aaf52c4212d245da3f8',
41 | '0x1acba73121d5f63d8ea40bdc64edb594bd88ed09',
42 | '0x7d7e813082ef6c143277c71786e5be626ec77b20',
43 | ]
44 |
45 | // warnings to display if page contains info about blocked token
46 | export const BLOCKED_WARNINGS = {
47 | '0xf4eda77f0b455a12f3eb44f8653835f377e36b76':
48 | 'TikTok Inc. has asserted this token is violating its trademarks and therefore is not available.',
49 | }
50 |
51 | /**
52 | * For tokens that cause erros on fee calculations
53 | */
54 | export const FEE_WARNING_TOKENS = ['0xd46ba6d942050d489dbd938a2c909a5d5039a161']
55 |
56 | export const UNTRACKED_COPY = 'Derived USD values may be inaccurate without liquid stablecoin or ETH pairings.'
57 |
58 | // pairs that should be tracked but arent due to lag in subgraph
59 | export const TRACKED_OVERRIDES_PAIRS = [
60 | '0x9928e4046d7c6513326ccea028cd3e7a91c7590a',
61 | '0x87da823b6fc8eb8575a235a824690fda94674c88',
62 | '0xcd7989894bc033581532d2cd88da5db0a4b12859',
63 | '0xe1573b9d29e2183b1af0e743dc2754979a40d237',
64 | '0x45804880de22913dafe09f4980848ece6ecbaf78',
65 | '0x709f7b10f22eb62b05913b59b92ddd372d4e2152',
66 | ]
67 |
68 | // tokens that should be tracked but arent due to lag in subgraph
69 | // all pairs that include token will be tracked
70 | export const TRACKED_OVERRIDES_TOKENS = ['0x956f47f50a910163d8bf957cf5846d573e7f87ca']
71 |
--------------------------------------------------------------------------------
/src/contexts/Application.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, useContext, useReducer, useMemo, useCallback, useState, useEffect } from 'react'
2 | import { timeframeOptions, SUPPORTED_LIST_URLS__NO_ENS } from '../constants'
3 | import dayjs from 'dayjs'
4 | import utc from 'dayjs/plugin/utc'
5 | import getTokenList from '../utils/tokenLists'
6 | import { healthClient } from '../apollo/client'
7 | import { SUBGRAPH_HEALTH } from '../apollo/queries'
8 | dayjs.extend(utc)
9 |
10 | const UPDATE = 'UPDATE'
11 | const UPDATE_TIMEFRAME = 'UPDATE_TIMEFRAME'
12 | const UPDATE_SESSION_START = 'UPDATE_SESSION_START'
13 | const UPDATED_SUPPORTED_TOKENS = 'UPDATED_SUPPORTED_TOKENS'
14 | const UPDATE_LATEST_BLOCK = 'UPDATE_LATEST_BLOCK'
15 | const UPDATE_HEAD_BLOCK = 'UPDATE_HEAD_BLOCK'
16 |
17 | const SUPPORTED_TOKENS = 'SUPPORTED_TOKENS'
18 | const TIME_KEY = 'TIME_KEY'
19 | const CURRENCY = 'CURRENCY'
20 | const SESSION_START = 'SESSION_START'
21 | const LATEST_BLOCK = 'LATEST_BLOCK'
22 | const HEAD_BLOCK = 'HEAD_BLOCK'
23 |
24 | const ApplicationContext = createContext()
25 |
26 | function useApplicationContext() {
27 | return useContext(ApplicationContext)
28 | }
29 |
30 | function reducer(state, { type, payload }) {
31 | switch (type) {
32 | case UPDATE: {
33 | const { currency } = payload
34 | return {
35 | ...state,
36 | [CURRENCY]: currency,
37 | }
38 | }
39 | case UPDATE_TIMEFRAME: {
40 | const { newTimeFrame } = payload
41 | return {
42 | ...state,
43 | [TIME_KEY]: newTimeFrame,
44 | }
45 | }
46 | case UPDATE_SESSION_START: {
47 | const { timestamp } = payload
48 | return {
49 | ...state,
50 | [SESSION_START]: timestamp,
51 | }
52 | }
53 |
54 | case UPDATE_LATEST_BLOCK: {
55 | const { block } = payload
56 | return {
57 | ...state,
58 | [LATEST_BLOCK]: block,
59 | }
60 | }
61 |
62 | case UPDATE_HEAD_BLOCK: {
63 | const { block } = payload
64 | return {
65 | ...state,
66 | [HEAD_BLOCK]: block,
67 | }
68 | }
69 |
70 | case UPDATED_SUPPORTED_TOKENS: {
71 | const { supportedTokens } = payload
72 | return {
73 | ...state,
74 | [SUPPORTED_TOKENS]: supportedTokens,
75 | }
76 | }
77 |
78 | default: {
79 | throw Error(`Unexpected action type in DataContext reducer: '${type}'.`)
80 | }
81 | }
82 | }
83 |
84 | const INITIAL_STATE = {
85 | CURRENCY: 'USD',
86 | TIME_KEY: timeframeOptions.ALL_TIME,
87 | }
88 |
89 | export default function Provider({ children }) {
90 | const [state, dispatch] = useReducer(reducer, INITIAL_STATE)
91 | const update = useCallback((currency) => {
92 | dispatch({
93 | type: UPDATE,
94 | payload: {
95 | currency,
96 | },
97 | })
98 | }, [])
99 |
100 | // global time window for charts - see timeframe options in constants
101 | const updateTimeframe = useCallback((newTimeFrame) => {
102 | dispatch({
103 | type: UPDATE_TIMEFRAME,
104 | payload: {
105 | newTimeFrame,
106 | },
107 | })
108 | }, [])
109 |
110 | // used for refresh button
111 | const updateSessionStart = useCallback((timestamp) => {
112 | dispatch({
113 | type: UPDATE_SESSION_START,
114 | payload: {
115 | timestamp,
116 | },
117 | })
118 | }, [])
119 |
120 | const updateSupportedTokens = useCallback((supportedTokens) => {
121 | dispatch({
122 | type: UPDATED_SUPPORTED_TOKENS,
123 | payload: {
124 | supportedTokens,
125 | },
126 | })
127 | }, [])
128 |
129 | const updateLatestBlock = useCallback((block) => {
130 | dispatch({
131 | type: UPDATE_LATEST_BLOCK,
132 | payload: {
133 | block,
134 | },
135 | })
136 | }, [])
137 |
138 | const updateHeadBlock = useCallback((block) => {
139 | dispatch({
140 | type: UPDATE_HEAD_BLOCK,
141 | payload: {
142 | block,
143 | },
144 | })
145 | }, [])
146 |
147 | return (
148 | [
151 | state,
152 | {
153 | update,
154 | updateSessionStart,
155 | updateTimeframe,
156 | updateSupportedTokens,
157 | updateLatestBlock,
158 | updateHeadBlock,
159 | },
160 | ],
161 | [state, update, updateTimeframe, updateSessionStart, updateSupportedTokens, updateLatestBlock, updateHeadBlock]
162 | )}
163 | >
164 | {children}
165 |
166 | )
167 | }
168 |
169 | export function useLatestBlocks() {
170 | const [state, { updateLatestBlock, updateHeadBlock }] = useApplicationContext()
171 |
172 | const latestBlock = state?.[LATEST_BLOCK]
173 | const headBlock = state?.[HEAD_BLOCK]
174 |
175 | useEffect(() => {
176 | async function fetch() {
177 | healthClient
178 | .query({
179 | query: SUBGRAPH_HEALTH,
180 | })
181 | .then((res) => {
182 | const syncedBlock = res.data.indexingStatusForCurrentVersion.chains[0].latestBlock.number
183 | const headBlock = res.data.indexingStatusForCurrentVersion.chains[0].chainHeadBlock.number
184 | if (syncedBlock && headBlock) {
185 | updateLatestBlock(syncedBlock)
186 | updateHeadBlock(headBlock)
187 | }
188 | })
189 | .catch((e) => {
190 | console.log(e)
191 | })
192 | }
193 | if (!latestBlock) {
194 | fetch()
195 | }
196 | }, [latestBlock, updateHeadBlock, updateLatestBlock])
197 |
198 | return [latestBlock, headBlock]
199 | }
200 |
201 | export function useCurrentCurrency() {
202 | const [state, { update }] = useApplicationContext()
203 | const toggleCurrency = useCallback(() => {
204 | if (state.currency === 'ETH') {
205 | update('USD')
206 | } else {
207 | update('ETH')
208 | }
209 | }, [state, update])
210 | return [state[CURRENCY], toggleCurrency]
211 | }
212 |
213 | export function useTimeframe() {
214 | const [state, { updateTimeframe }] = useApplicationContext()
215 | const activeTimeframe = state?.[`TIME_KEY`]
216 | return [activeTimeframe, updateTimeframe]
217 | }
218 |
219 | export function useStartTimestamp() {
220 | const [activeWindow] = useTimeframe()
221 | const [startDateTimestamp, setStartDateTimestamp] = useState()
222 |
223 | // monitor the old date fetched
224 | useEffect(() => {
225 | let startTime =
226 | dayjs
227 | .utc()
228 | .subtract(
229 | 1,
230 | activeWindow === timeframeOptions.week ? 'week' : activeWindow === timeframeOptions.ALL_TIME ? 'year' : 'year'
231 | )
232 | .startOf('day')
233 | .unix() - 1
234 | // if we find a new start time less than the current startrtime - update oldest pooint to fetch
235 | setStartDateTimestamp(startTime)
236 | }, [activeWindow, startDateTimestamp])
237 |
238 | return startDateTimestamp
239 | }
240 |
241 | // keep track of session length for refresh ticker
242 | export function useSessionStart() {
243 | const [state, { updateSessionStart }] = useApplicationContext()
244 | const sessionStart = state?.[SESSION_START]
245 |
246 | useEffect(() => {
247 | if (!sessionStart) {
248 | updateSessionStart(Date.now())
249 | }
250 | })
251 |
252 | const [seconds, setSeconds] = useState(0)
253 |
254 | useEffect(() => {
255 | let interval = null
256 | interval = setInterval(() => {
257 | setSeconds(Date.now() - sessionStart ?? Date.now())
258 | }, 1000)
259 |
260 | return () => clearInterval(interval)
261 | }, [seconds, sessionStart])
262 |
263 | return parseInt(seconds / 1000)
264 | }
265 |
266 | export function useListedTokens() {
267 | const [state, { updateSupportedTokens }] = useApplicationContext()
268 | const supportedTokens = state?.[SUPPORTED_TOKENS]
269 |
270 | useEffect(() => {
271 | async function fetchList() {
272 | const allFetched = await SUPPORTED_LIST_URLS__NO_ENS.reduce(async (fetchedTokens, url) => {
273 | const tokensSoFar = await fetchedTokens
274 | const newTokens = await getTokenList(url)
275 | if (newTokens?.tokens) {
276 | return Promise.resolve([...tokensSoFar, ...newTokens.tokens])
277 | }
278 | }, Promise.resolve([]))
279 | let formatted = allFetched?.map((t) => t.address.toLowerCase())
280 | updateSupportedTokens(formatted)
281 | }
282 | if (!supportedTokens) {
283 | try {
284 | fetchList()
285 | } catch {
286 | console.log('Error fetching')
287 | }
288 | }
289 | }, [updateSupportedTokens, supportedTokens])
290 |
291 | return supportedTokens
292 | }
293 |
--------------------------------------------------------------------------------
/src/contexts/LocalStorage.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
2 |
3 | const UNISWAP = 'UNISWAP'
4 |
5 | const VERSION = 'VERSION'
6 | const CURRENT_VERSION = 0
7 | const LAST_SAVED = 'LAST_SAVED'
8 | const DISMISSED_PATHS = 'DISMISSED_PATHS'
9 | const SAVED_ACCOUNTS = 'SAVED_ACCOUNTS'
10 | const SAVED_TOKENS = 'SAVED_TOKENS'
11 | const SAVED_PAIRS = 'SAVED_PAIRS'
12 |
13 | const DARK_MODE = 'DARK_MODE'
14 |
15 | const UPDATABLE_KEYS = [DARK_MODE, DISMISSED_PATHS, SAVED_ACCOUNTS, SAVED_PAIRS, SAVED_TOKENS]
16 |
17 | const UPDATE_KEY = 'UPDATE_KEY'
18 |
19 | const LocalStorageContext = createContext()
20 |
21 | function useLocalStorageContext() {
22 | return useContext(LocalStorageContext)
23 | }
24 |
25 | function reducer(state, { type, payload }) {
26 | switch (type) {
27 | case UPDATE_KEY: {
28 | const { key, value } = payload
29 | if (!UPDATABLE_KEYS.some((k) => k === key)) {
30 | throw Error(`Unexpected key in LocalStorageContext reducer: '${key}'.`)
31 | } else {
32 | return {
33 | ...state,
34 | [key]: value,
35 | }
36 | }
37 | }
38 | default: {
39 | throw Error(`Unexpected action type in LocalStorageContext reducer: '${type}'.`)
40 | }
41 | }
42 | }
43 |
44 | function init() {
45 | const defaultLocalStorage = {
46 | [VERSION]: CURRENT_VERSION,
47 | [DARK_MODE]: true,
48 | [DISMISSED_PATHS]: {},
49 | [SAVED_ACCOUNTS]: [],
50 | [SAVED_TOKENS]: {},
51 | [SAVED_PAIRS]: {},
52 | }
53 |
54 | try {
55 | const parsed = JSON.parse(window.localStorage.getItem(UNISWAP))
56 | if (parsed[VERSION] !== CURRENT_VERSION) {
57 | // this is where we could run migration logic
58 | return defaultLocalStorage
59 | } else {
60 | return { ...defaultLocalStorage, ...parsed }
61 | }
62 | } catch {
63 | return defaultLocalStorage
64 | }
65 | }
66 |
67 | export default function Provider({ children }) {
68 | const [state, dispatch] = useReducer(reducer, undefined, init)
69 |
70 | const updateKey = useCallback((key, value) => {
71 | dispatch({ type: UPDATE_KEY, payload: { key, value } })
72 | }, [])
73 |
74 | return (
75 | [state, { updateKey }], [state, updateKey])}>
76 | {children}
77 |
78 | )
79 | }
80 |
81 | export function Updater() {
82 | const [state] = useLocalStorageContext()
83 |
84 | useEffect(() => {
85 | window.localStorage.setItem(UNISWAP, JSON.stringify({ ...state, [LAST_SAVED]: Math.floor(Date.now() / 1000) }))
86 | })
87 |
88 | return null
89 | }
90 |
91 | export function useDarkModeManager() {
92 | const [state, { updateKey }] = useLocalStorageContext()
93 | let isDarkMode = state[DARK_MODE]
94 | const toggleDarkMode = useCallback(
95 | (value) => {
96 | updateKey(DARK_MODE, value === false || value === true ? value : !isDarkMode)
97 | },
98 | [updateKey, isDarkMode]
99 | )
100 | return [isDarkMode, toggleDarkMode]
101 | }
102 |
103 | export function usePathDismissed(path) {
104 | const [state, { updateKey }] = useLocalStorageContext()
105 | const pathDismissed = state?.[DISMISSED_PATHS]?.[path]
106 | function dismiss() {
107 | let newPaths = state?.[DISMISSED_PATHS]
108 | newPaths[path] = true
109 | updateKey(DISMISSED_PATHS, newPaths)
110 | }
111 |
112 | return [pathDismissed, dismiss]
113 | }
114 |
115 | export function useSavedAccounts() {
116 | const [state, { updateKey }] = useLocalStorageContext()
117 | const savedAccounts = state?.[SAVED_ACCOUNTS]
118 |
119 | const addAccount = useCallback(
120 | (account) => {
121 | updateKey(SAVED_ACCOUNTS, [...(savedAccounts ?? []), account])
122 | },
123 | [savedAccounts, updateKey]
124 | )
125 |
126 | const removeAccount = useCallback(
127 | (account) => {
128 | let index = savedAccounts?.indexOf(account) ?? -1
129 | if (index > -1) {
130 | updateKey(SAVED_ACCOUNTS, [
131 | ...savedAccounts.slice(0, index),
132 | ...savedAccounts.slice(index + 1, savedAccounts.length),
133 | ])
134 | }
135 | },
136 | [savedAccounts, updateKey]
137 | )
138 |
139 | return [savedAccounts, addAccount, removeAccount]
140 | }
141 |
142 | export function useSavedPairs() {
143 | const [state, { updateKey }] = useLocalStorageContext()
144 | const savedPairs = state?.[SAVED_PAIRS]
145 |
146 | function addPair(address, token0Address, token1Address, token0Symbol, token1Symbol) {
147 | let newList = state?.[SAVED_PAIRS]
148 | newList[address] = {
149 | address,
150 | token0Address,
151 | token1Address,
152 | token0Symbol,
153 | token1Symbol,
154 | }
155 | updateKey(SAVED_PAIRS, newList)
156 | }
157 |
158 | function removePair(address) {
159 | let newList = state?.[SAVED_PAIRS]
160 | newList[address] = null
161 | updateKey(SAVED_PAIRS, newList)
162 | }
163 |
164 | return [savedPairs, addPair, removePair]
165 | }
166 |
167 | export function useSavedTokens() {
168 | const [state, { updateKey }] = useLocalStorageContext()
169 | const savedTokens = state?.[SAVED_TOKENS]
170 |
171 | function addToken(address, symbol) {
172 | let newList = state?.[SAVED_TOKENS]
173 | newList[address] = {
174 | symbol,
175 | }
176 | updateKey(SAVED_TOKENS, newList)
177 | }
178 |
179 | function removeToken(address) {
180 | let newList = state?.[SAVED_TOKENS]
181 | newList[address] = null
182 | updateKey(SAVED_TOKENS, newList)
183 | }
184 |
185 | return [savedTokens, addToken, removeToken]
186 | }
187 |
--------------------------------------------------------------------------------
/src/contexts/V1Data.js:
--------------------------------------------------------------------------------
1 | import { v1Client } from '../apollo/client'
2 | import dayjs from 'dayjs'
3 | import utc from 'dayjs/plugin/utc'
4 | import { getPercentChange, get2DayPercentChange } from '../utils'
5 | import { V1_DATA_QUERY } from '../apollo/queries'
6 | import weekOfYear from 'dayjs/plugin/weekOfYear'
7 |
8 | dayjs.extend(utc)
9 | dayjs.extend(weekOfYear)
10 |
11 | export async function getV1Data() {
12 | dayjs.extend(utc)
13 |
14 | const utcCurrentTime = dayjs()
15 | const utcOneDayBack = utcCurrentTime.subtract(1, 'day').unix()
16 | const utcTwoDaysBack = utcCurrentTime.subtract(2, 'day').unix()
17 |
18 | try {
19 | // get the current data
20 | let result = await v1Client.query({
21 | query: V1_DATA_QUERY,
22 | variables: {
23 | date: utcOneDayBack,
24 | date2: utcTwoDaysBack,
25 | },
26 | fetchPolicy: 'cache-first',
27 | })
28 |
29 | let data = result.data.current
30 | let oneDayData = result.data.oneDay[0]
31 | let twoDayData = result.data.twoDay[0]
32 |
33 | let [volumeChangeUSD, volumePercentChangeUSD] = get2DayPercentChange(
34 | data.totalVolumeUSD,
35 | oneDayData.totalVolumeUSD,
36 | twoDayData.totalVolumeUSD
37 | )
38 |
39 | let [txCountChange, txCountPercentChange] = get2DayPercentChange(
40 | data.txCount,
41 | oneDayData.txCount,
42 | twoDayData.txCount
43 | )
44 |
45 | // regular percent changes
46 | let liquidityPercentChangeUSD = getPercentChange(data.liquidityUsd, oneDayData.liquidityUsd)
47 |
48 | data.liquidityPercentChangeUSD = liquidityPercentChangeUSD
49 | data.volumePercentChangeUSD = volumePercentChangeUSD
50 | data.txCount = txCountChange
51 | data.txCountPercentChange = txCountPercentChange
52 | data.dailyVolumeUSD = volumeChangeUSD
53 |
54 | return data
55 | } catch (err) {
56 | console.log('error: ', err)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/hooks/index.ts:
--------------------------------------------------------------------------------
1 | import { useState, useCallback, useEffect, useRef } from 'react'
2 | import { shade } from 'polished'
3 | import Vibrant from 'node-vibrant'
4 | import { hex } from 'wcag-contrast'
5 | import { isAddress } from '../utils'
6 | import copy from 'copy-to-clipboard'
7 |
8 | export function useColor(tokenAddress, token) {
9 | const [color, setColor] = useState('#2172E5')
10 | if (tokenAddress) {
11 | const path = `https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/${isAddress(
12 | tokenAddress
13 | )}/logo.png`
14 | if (path) {
15 | Vibrant.from(path).getPalette((err, palette) => {
16 | if (palette && palette.Vibrant) {
17 | let detectedHex = palette.Vibrant.hex
18 | let AAscore = hex(detectedHex, '#FFF')
19 | while (AAscore < 3) {
20 | detectedHex = shade(0.005, detectedHex)
21 | AAscore = hex(detectedHex, '#FFF')
22 | }
23 | if (token === 'DAI') {
24 | setColor('#FAAB14')
25 | } else {
26 | setColor(detectedHex)
27 | }
28 | }
29 | })
30 | }
31 | }
32 | return color
33 | }
34 |
35 | export function useCopyClipboard(timeout = 500) {
36 | const [isCopied, setIsCopied] = useState(false)
37 |
38 | const staticCopy = useCallback((text) => {
39 | const didCopy = copy(text)
40 | setIsCopied(didCopy)
41 | }, [])
42 |
43 | useEffect(() => {
44 | if (isCopied) {
45 | const hide = setTimeout(() => {
46 | setIsCopied(false)
47 | }, timeout)
48 |
49 | return () => {
50 | clearTimeout(hide)
51 | }
52 | }
53 | }, [isCopied, setIsCopied, timeout])
54 |
55 | return [isCopied, staticCopy]
56 | }
57 |
58 | export const useOutsideClick = (ref, ref2, callback) => {
59 | const handleClick = (e) => {
60 | if (ref.current && ref.current && !ref2.current) {
61 | callback(true)
62 | } else if (ref.current && !ref.current.contains(e.target) && ref2.current && !ref2.current.contains(e.target)) {
63 | callback(true)
64 | } else {
65 | callback(false)
66 | }
67 | }
68 | useEffect(() => {
69 | document.addEventListener('click', handleClick)
70 | return () => {
71 | document.removeEventListener('click', handleClick)
72 | }
73 | })
74 | }
75 |
76 | export default function useInterval(callback: () => void, delay: null | number) {
77 | const savedCallback = useRef<() => void>()
78 |
79 | // Remember the latest callback.
80 | useEffect(() => {
81 | savedCallback.current = callback
82 | }, [callback])
83 |
84 | // Set up the interval.
85 | useEffect(() => {
86 | function tick() {
87 | const current = savedCallback.current
88 | current && current()
89 | }
90 |
91 | if (delay !== null) {
92 | tick()
93 | const id = setInterval(tick, delay)
94 | return () => clearInterval(id)
95 | }
96 | return
97 | }, [delay])
98 | }
99 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import ReactGA from 'react-ga'
4 | import { isMobile } from 'react-device-detect'
5 | import ThemeProvider, { GlobalStyle } from './Theme'
6 | import LocalStorageContextProvider, { Updater as LocalStorageContextUpdater } from './contexts/LocalStorage'
7 | import TokenDataContextProvider, { Updater as TokenDataContextUpdater } from './contexts/TokenData'
8 | import GlobalDataContextProvider from './contexts/GlobalData'
9 | import PairDataContextProvider, { Updater as PairDataContextUpdater } from './contexts/PairData'
10 | import ApplicationContextProvider from './contexts/Application'
11 | import UserContextProvider from './contexts/User'
12 | import App from './App'
13 |
14 | // initialize GA
15 | const GOOGLE_ANALYTICS_ID = process.env.REACT_APP_GOOGLE_ANALYTICS_ID
16 |
17 | if (typeof GOOGLE_ANALYTICS_ID === 'string') {
18 | ReactGA.initialize(GOOGLE_ANALYTICS_ID, {
19 | gaOptions: {
20 | storage: 'none',
21 | storeGac: false,
22 | },
23 | })
24 | ReactGA.set({
25 | anonymizeIp: true,
26 | customBrowserType: !isMobile
27 | ? 'desktop'
28 | : 'web3' in window || 'ethereum' in window
29 | ? 'mobileWeb3'
30 | : 'mobileRegular',
31 | })
32 | } else {
33 | ReactGA.initialize('test', { testMode: true, debug: true })
34 | }
35 |
36 | function ContextProviders({ children }) {
37 | return (
38 |
39 |
40 |
41 |
42 |
43 | {children}
44 |
45 |
46 |
47 |
48 |
49 | )
50 | }
51 |
52 | function Updaters() {
53 | return (
54 | <>
55 |
56 |
57 |
58 | >
59 | )
60 | }
61 |
62 | ReactDOM.render(
63 |
64 |
65 |
66 | <>
67 |
68 |
69 | >
70 |
71 | ,
72 | document.getElementById('root')
73 | )
74 |
--------------------------------------------------------------------------------
/src/pages/AccountLookup.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import 'feather-icons'
3 | import { withRouter } from 'react-router-dom'
4 | import { TYPE } from '../Theme'
5 | import { PageWrapper, FullWrapper } from '../components'
6 | import Panel from '../components/Panel'
7 | import LPList from '../components/LPList'
8 | import styled from 'styled-components'
9 | import AccountSearch from '../components/AccountSearch'
10 | import { useTopLps } from '../contexts/GlobalData'
11 | import LocalLoader from '../components/LocalLoader'
12 | import { RowBetween } from '../components/Row'
13 | import { useMedia } from 'react-use'
14 | import Search from '../components/Search'
15 |
16 | const AccountWrapper = styled.div`
17 | @media screen and (max-width: 600px) {
18 | width: 100%;
19 | }
20 | `
21 |
22 | function AccountLookup() {
23 | // scroll to top
24 | useEffect(() => {
25 | window.scrollTo(0, 0)
26 | }, [])
27 |
28 | const topLps = useTopLps()
29 |
30 | const below600 = useMedia('(max-width: 600px)')
31 |
32 | return (
33 |
34 |
35 |
36 | Wallet analytics
37 | {!below600 && }
38 |
39 |
40 |
41 |
42 |
43 | Top Liquidity Positions
44 |
45 | {topLps && topLps.length > 0 ? : }
46 |
47 |
48 | )
49 | }
50 |
51 | export default withRouter(AccountLookup)
52 |
--------------------------------------------------------------------------------
/src/pages/AllPairsPage.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import 'feather-icons'
3 |
4 | import { TYPE } from '../Theme'
5 | import Panel from '../components/Panel'
6 | import { useAllPairData } from '../contexts/PairData'
7 | import PairList from '../components/PairList'
8 | import { PageWrapper, FullWrapper } from '../components'
9 | import { RowBetween, AutoRow } from '../components/Row'
10 | import Search from '../components/Search'
11 | import { useMedia } from 'react-use'
12 | import QuestionHelper from '../components/QuestionHelper'
13 | import CheckBox from '../components/Checkbox'
14 |
15 | function AllPairsPage() {
16 | const allPairs = useAllPairData()
17 |
18 | useEffect(() => {
19 | window.scrollTo(0, 0)
20 | }, [])
21 |
22 | const below800 = useMedia('(max-width: 800px)')
23 |
24 | const [useTracked, setUseTracked] = useState(true)
25 |
26 | return (
27 |
28 |
29 |
30 | Top Pairs
31 | {!below800 && }
32 |
33 |
34 | setUseTracked(!useTracked)} text={'Hide untracked pairs'} />
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | )
43 | }
44 |
45 | export default AllPairsPage
46 |
--------------------------------------------------------------------------------
/src/pages/AllTokensPage.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import 'feather-icons'
3 |
4 | import TopTokenList from '../components/TokenList'
5 | import { TYPE } from '../Theme'
6 | import Panel from '../components/Panel'
7 | import { useAllTokenData } from '../contexts/TokenData'
8 | import { PageWrapper, FullWrapper } from '../components'
9 | import { RowBetween } from '../components/Row'
10 | import Search from '../components/Search'
11 | import { useMedia } from 'react-use'
12 | // import CheckBox from '../components/Checkbox'
13 | // import QuestionHelper from '../components/QuestionHelper'
14 |
15 | function AllTokensPage() {
16 | const allTokens = useAllTokenData()
17 |
18 | useEffect(() => {
19 | window.scrollTo(0, 0)
20 | }, [])
21 |
22 | const below600 = useMedia('(max-width: 800px)')
23 |
24 | // const [useTracked, setUseTracked] = useState(true)
25 |
26 | return (
27 |
28 |
29 |
30 | Top Tokens
31 | {!below600 && }
32 |
33 | {/*
34 | setUseTracked(!useTracked)} text={'Hide untracked tokens'} />
35 |
36 | */}
37 |
38 |
39 |
40 |
41 |
42 | )
43 | }
44 |
45 | export default AllTokensPage
46 |
--------------------------------------------------------------------------------
/src/pages/GlobalPage.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import { withRouter } from 'react-router-dom'
3 | import { Box } from 'rebass'
4 | import styled from 'styled-components'
5 |
6 | import { AutoRow, RowBetween } from '../components/Row'
7 | import { AutoColumn } from '../components/Column'
8 | import PairList from '../components/PairList'
9 | import TopTokenList from '../components/TokenList'
10 | import TxnList from '../components/TxnList'
11 | import GlobalChart from '../components/GlobalChart'
12 | import Search from '../components/Search'
13 | import GlobalStats from '../components/GlobalStats'
14 |
15 | import { useGlobalData, useGlobalTransactions } from '../contexts/GlobalData'
16 | import { useAllPairData } from '../contexts/PairData'
17 | import { useMedia } from 'react-use'
18 | import Panel from '../components/Panel'
19 | import { useAllTokenData } from '../contexts/TokenData'
20 | import { formattedNum, formattedPercent } from '../utils'
21 | import { TYPE, ThemedBackground } from '../Theme'
22 | import { transparentize } from 'polished'
23 | import { CustomLink } from '../components/Link'
24 |
25 | import { PageWrapper, ContentWrapper } from '../components'
26 | import CheckBox from '../components/Checkbox'
27 | import QuestionHelper from '../components/QuestionHelper'
28 |
29 | const ListOptions = styled(AutoRow)`
30 | height: 40px;
31 | width: 100%;
32 | font-size: 1.25rem;
33 | font-weight: 600;
34 |
35 | @media screen and (max-width: 640px) {
36 | font-size: 1rem;
37 | }
38 | `
39 |
40 | const GridRow = styled.div`
41 | display: grid;
42 | width: 100%;
43 | grid-template-columns: 1fr 1fr;
44 | column-gap: 6px;
45 | align-items: start;
46 | justify-content: space-between;
47 | `
48 |
49 | function GlobalPage() {
50 | // get data for lists and totals
51 | const allPairs = useAllPairData()
52 | const allTokens = useAllTokenData()
53 | const transactions = useGlobalTransactions()
54 | const { totalLiquidityUSD, oneDayVolumeUSD, volumeChangeUSD, liquidityChangeUSD } = useGlobalData()
55 |
56 | // breakpoints
57 | const below800 = useMedia('(max-width: 800px)')
58 |
59 | // scrolling refs
60 | useEffect(() => {
61 | document.querySelector('body').scrollTo({
62 | behavior: 'smooth',
63 | top: 0,
64 | })
65 | }, [])
66 |
67 | // for tracked data on pairs
68 | const [useTracked, setUseTracked] = useState(true)
69 |
70 | return (
71 |
72 |
73 |
74 |
75 |
76 | {below800 ? 'Uniswap Analytics' : 'Uniswap Analytics'}
77 |
78 |
79 |
80 | {below800 && ( // mobile card
81 |
82 |
83 |
84 |
85 |
86 |
87 | Volume (24hrs)
88 |
89 |
90 |
91 |
92 | {oneDayVolumeUSD ? formattedNum(oneDayVolumeUSD, true) : '-'}
93 |
94 | {volumeChangeUSD ? formattedPercent(volumeChangeUSD) : '-'}
95 |
96 |
97 |
98 |
99 | Total Liquidity
100 |
101 |
102 |
103 |
104 | {totalLiquidityUSD ? formattedNum(totalLiquidityUSD, true) : '-'}
105 |
106 |
107 | {liquidityChangeUSD ? formattedPercent(liquidityChangeUSD) : '-'}
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 | )}
116 | {!below800 && (
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 | )}
126 | {below800 && (
127 |
128 |
129 |
130 |
131 |
132 | )}
133 |
134 |
135 |
136 | Top Tokens
137 |
138 | See All
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 | Top Pairs
148 |
149 |
150 | setUseTracked(!useTracked)}
153 | text={'Hide untracked pairs'}
154 | />
155 |
156 | See All
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 | Transactions
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 | )
175 | }
176 |
177 | export default withRouter(GlobalPage)
178 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/utils/data.ts:
--------------------------------------------------------------------------------
1 | interface BasicData {
2 | token0?: {
3 | id: string
4 | name: string
5 | symbol: string
6 | }
7 | token1?: {
8 | id: string
9 | name: string
10 | symbol: string
11 | }
12 | }
13 |
14 | // Override data return from graph - usually because proxy token has changed
15 | // names since entitiy was created in subgraph
16 | // keys are lowercase token addresses <--------
17 | const TOKEN_OVERRIDES: { [address: string]: { name: string; symbol: string } } = {
18 | '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2': {
19 | name: 'Ether (Wrapped)',
20 | symbol: 'ETH',
21 | },
22 | '0x1416946162b1c2c871a73b07e932d2fb6c932069': {
23 | name: 'Energi',
24 | symbol: 'NRGE',
25 | },
26 | }
27 |
28 | // override tokens with incorrect symbol or names
29 | export function updateNameData(data: BasicData): BasicData | undefined {
30 | if (data?.token0?.id && Object.keys(TOKEN_OVERRIDES).includes(data.token0.id)) {
31 | data.token0.name = TOKEN_OVERRIDES[data.token0.id].name
32 | data.token0.symbol = TOKEN_OVERRIDES[data.token0.id].symbol
33 | }
34 |
35 | if (data?.token1?.id && Object.keys(TOKEN_OVERRIDES).includes(data.token1.id)) {
36 | data.token1.name = TOKEN_OVERRIDES[data.token1.id].name
37 | data.token1.symbol = TOKEN_OVERRIDES[data.token1.id].symbol
38 | }
39 |
40 | return data
41 | }
42 |
--------------------------------------------------------------------------------
/src/utils/tokenLists.ts:
--------------------------------------------------------------------------------
1 | import { TokenList } from '@uniswap/token-lists'
2 | import schema from '@uniswap/token-lists/src/tokenlist.schema.json'
3 | import Ajv from 'ajv'
4 |
5 | /**
6 | * Given a URI that may be ipfs, ipns, http, or https protocol, return the fetch-able http(s) URLs for the same content
7 | * @param uri to convert to fetch-able http url
8 | */
9 | function uriToHttp(uri: string): string[] {
10 | const protocol = uri.split(':')[0].toLowerCase()
11 | switch (protocol) {
12 | case 'https':
13 | return [uri]
14 | case 'http':
15 | return ['https' + uri.substr(4), uri]
16 | case 'ipfs':
17 | const hash = uri.match(/^ipfs:(\/\/)?(.*)$/i)?.[2]
18 | return [`https://cloudflare-ipfs.com/ipfs/${hash}/`, `https://ipfs.io/ipfs/${hash}/`]
19 | case 'ipns':
20 | const name = uri.match(/^ipns:(\/\/)?(.*)$/i)?.[2]
21 | return [`https://cloudflare-ipfs.com/ipns/${name}/`, `https://ipfs.io/ipns/${name}/`]
22 | default:
23 | return []
24 | }
25 | }
26 |
27 | const tokenListValidator = new Ajv({ allErrors: true }).compile(schema)
28 |
29 | /**
30 | * Contains the logic for resolving a list URL to a validated token list
31 | * @param listUrl list url
32 | */
33 | export default async function getTokenList(listUrl: string): Promise {
34 | const urls = uriToHttp(listUrl)
35 | for (let i = 0; i < urls.length; i++) {
36 | const url = urls[i]
37 | const isLast = i === urls.length - 1
38 | let response
39 | try {
40 | response = await fetch(url)
41 | } catch (error) {
42 | console.debug('Failed to fetch list', listUrl, error)
43 |
44 | continue
45 | }
46 |
47 | if (!response.ok) {
48 | if (isLast) throw new Error(`Failed to download list ${listUrl}`)
49 | continue
50 | }
51 |
52 | const json = await response.json()
53 | if (!tokenListValidator(json)) {
54 | const validationErrors: string =
55 | tokenListValidator.errors?.reduce((memo, error) => {
56 | const add = `${error.dataPath} ${error.message ?? ''}`
57 | return memo.length > 0 ? `${memo}; ${add}` : `${add}`
58 | }, '') ?? 'unknown error'
59 | throw new Error(`Token list failed validation: ${validationErrors}`)
60 | }
61 | return json
62 | }
63 | // throw new Error('Unrecognized list URL protocol.')
64 | }
65 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": false,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "downlevelIteration": true,
17 | "allowSyntheticDefaultImports": true,
18 | "types": ["react-spring", "jest"]
19 | },
20 | "exclude": ["node_modules", "cypress"],
21 | "include": ["**/*.js", "**/*.ts", "**/*.tsx", "src/components/analytics/GoogleAnalyticsReporter.jsx"]
22 | }
23 |
--------------------------------------------------------------------------------
/tsconfig.strict.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "strict": true,
5 | "noImplicitAny": true,
6 | "alwaysStrict": true,
7 | "strictNullChecks": true,
8 | "noImplicitReturns": true,
9 | "noImplicitThis": true,
10 | "noUnusedLocals": true,
11 | "noFallthroughCasesInSwitch": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | module: {
3 | loaders: [
4 | {
5 | test: /\.(png|jpg|gif)$/,
6 | loader: 'url?limit=25000',
7 | },
8 | ],
9 | },
10 | }
11 |
--------------------------------------------------------------------------------