Compare commits
228 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8cdec6188c | ||
|
|
5325d0cbe5 | ||
|
|
c16e49e774 | ||
|
|
7e709e10db | ||
|
|
7389b178fd | ||
|
|
48f8c6a141 | ||
|
|
091876a374 | ||
|
|
d0e4aa832a | ||
|
|
b17a38d94b | ||
|
|
22136b2708 | ||
|
|
1897330ffc | ||
|
|
6131d0079f | ||
|
|
e6814994f6 | ||
|
|
fea7d3a867 | ||
|
|
f4f0f29409 | ||
|
|
fa25e3c3e5 | ||
|
|
51d2b3792f | ||
|
|
04ded04e74 | ||
|
|
cf188a86de | ||
|
|
f7e2435868 | ||
|
|
5817d3bbdb | ||
|
|
bdeb62ad64 | ||
|
|
e42effdbfd | ||
|
|
c0a5adf43d | ||
|
|
fd0489e654 | ||
|
|
caa0a2967d | ||
|
|
11ec2333dd | ||
|
|
251339a9ef | ||
|
|
148e415fe8 | ||
|
|
fd964c5b74 | ||
|
|
83b6eec271 | ||
|
|
cd76fffbbe | ||
|
|
2c0ac56296 | ||
|
|
f836e3ca32 | ||
|
|
1733fbb378 | ||
|
|
78142270a8 | ||
|
|
db4e2a9bee | ||
|
|
9cee94a473 | ||
|
|
db5a14387f | ||
|
|
5dc7d36669 | ||
|
|
a3cbe672c7 | ||
|
|
dc368ed7ac | ||
|
|
b109248b4c | ||
|
|
64cc6fb88c | ||
|
|
74f6a4ef3f | ||
|
|
f468001404 | ||
|
|
f26ec2ff1b | ||
|
|
61d1036d28 | ||
|
|
e11d2080a4 | ||
|
|
da33423719 | ||
|
|
bd4545538d | ||
|
|
4274db67d5 | ||
|
|
28498706cb | ||
|
|
68c71a67dd | ||
|
|
fe195b63f7 | ||
|
|
86b85e25a5 | ||
|
|
0ea635ce15 | ||
|
|
99ab581a87 | ||
|
|
fc571d0f63 | ||
|
|
2de43a8cdb | ||
|
|
5383436c88 | ||
|
|
521f3aae04 | ||
|
|
9318c1204b | ||
|
|
5055695b9b | ||
|
|
ae8c0377de | ||
|
|
8eaf1f4964 | ||
|
|
f717bf4a49 | ||
|
|
dcbd4e475d | ||
|
|
b704bdac94 | ||
|
|
00d3df95c0 | ||
|
|
251b8b703a | ||
|
|
ef8432437d | ||
|
|
71aebf33db | ||
|
|
5ff428b04b | ||
|
|
acb0c2056e | ||
|
|
0a4bcb62da | ||
|
|
f50bcbdb2d | ||
|
|
cbe421ee23 | ||
|
|
3439786c38 | ||
|
|
6294915be6 | ||
|
|
984c742d0e | ||
|
|
00b151d7fa | ||
|
|
5967cf5d9d | ||
|
|
e480f0ebe5 | ||
|
|
f6ceecbc5e | ||
|
|
a0348b45be | ||
|
|
e4b37cffcc | ||
|
|
dd69cccf91 | ||
|
|
8b228de88f | ||
|
|
f91fc3c6a6 | ||
|
|
e0e2b40f9f | ||
|
|
bc1c61b63a | ||
|
|
446ad3e0d4 | ||
|
|
65e58a08cf | ||
|
|
71b20b432c | ||
|
|
ecfa179b3f | ||
|
|
6c94a0f585 | ||
|
|
600aeaaff1 | ||
|
|
3bfbc74e47 | ||
|
|
84f76e34b2 | ||
|
|
b965bed865 | ||
|
|
a9039e8d0b | ||
|
|
60d35b46f3 | ||
|
|
3d422cf707 | ||
|
|
84c70ac84d | ||
|
|
de3a33dfcb | ||
|
|
99a084f230 | ||
|
|
e880955743 | ||
|
|
bbf43fcd27 | ||
|
|
a00ac56389 | ||
|
|
7201944bc2 | ||
|
|
b0ff0f83b0 | ||
|
|
5cf9e84db5 | ||
|
|
c0bdb8db12 | ||
|
|
2d8f767d74 | ||
|
|
1303416eca | ||
|
|
124f6420a5 | ||
|
|
91f5fc0881 | ||
|
|
ec831f8433 | ||
|
|
56bd9b68d7 | ||
|
|
865d21f039 | ||
|
|
dceadf8472 | ||
|
|
cd3a91bca8 | ||
|
|
de1f5d1adc | ||
|
|
b5d403768f | ||
|
|
c4c811aeb3 | ||
|
|
33c24a3f05 | ||
|
|
9ef2b3a116 | ||
|
|
afe38a2d10 | ||
|
|
d28607a1c8 | ||
|
|
7fb363ac46 | ||
|
|
16b0b1530d | ||
|
|
abb2696f40 | ||
|
|
772178fc86 | ||
|
|
9f1378f635 | ||
|
|
84275dcce1 | ||
|
|
a76ece6ce3 | ||
|
|
334e137fb3 | ||
|
|
eb6c4d464a | ||
|
|
24734e6a34 | ||
|
|
f1bcee3c08 | ||
|
|
7a215ccdb4 | ||
|
|
c5c4f48d96 | ||
|
|
bdcf761ddd | ||
|
|
e876267d83 | ||
|
|
76cbd82cb7 | ||
|
|
6c4f7ab9a1 | ||
|
|
da20315724 | ||
|
|
963b910552 | ||
|
|
9e2dc9a435 | ||
|
|
6567f18bf5 | ||
|
|
ee96973212 | ||
|
|
ce6c783174 | ||
|
|
64e8c3ced9 | ||
|
|
46e6c2295d | ||
|
|
3626dbdeec | ||
|
|
eb75e0dc2e | ||
|
|
c26ecdfc88 | ||
|
|
f508788026 | ||
|
|
377026bca8 | ||
|
|
9470c49d11 | ||
|
|
e1abd81a1d | ||
|
|
7d9657867d | ||
|
|
7cc52abb96 | ||
|
|
5ac41417b0 | ||
|
|
2c74c5f2df | ||
|
|
cbc2ff668e | ||
|
|
a73f59b4ff | ||
|
|
7a75626c31 | ||
|
|
a0e14bef10 | ||
|
|
9b5a53b2e8 | ||
|
|
50fdb36b6f | ||
|
|
b993902c73 | ||
|
|
828bf540ba | ||
|
|
7c88a5a008 | ||
|
|
360c5e2c96 | ||
|
|
72678ee667 | ||
|
|
4dd74f2144 | ||
|
|
e45c104135 | ||
|
|
98fcaacd9b | ||
|
|
93551579e4 | ||
|
|
8a9388ed81 | ||
|
|
884bf41da7 | ||
|
|
5b686aea97 | ||
|
|
c4a456a085 | ||
|
|
4b9098a7bf | ||
|
|
71a246f25c | ||
|
|
8de048bc84 | ||
|
|
163e2d5560 | ||
|
|
0edb0fe5e2 | ||
|
|
496408b3db | ||
|
|
78b6f5c72d | ||
|
|
f9fb71a803 | ||
|
|
59d0046411 | ||
|
|
b4e0234d07 | ||
|
|
4a8dbda0b8 | ||
|
|
0cbb24c614 | ||
|
|
a9dba258ff | ||
|
|
fa163cb938 | ||
|
|
b52273932a | ||
|
|
9ad8f80e4e | ||
|
|
69bc598dea | ||
|
|
7feba045fc | ||
|
|
04cee0a07d | ||
|
|
ea73260e56 | ||
|
|
b4bd2973a9 | ||
|
|
a071b8adb0 | ||
|
|
610acb0191 | ||
|
|
63bad8f890 | ||
|
|
32f955693f | ||
|
|
96c66a5846 | ||
|
|
8c269a6d39 | ||
|
|
36f111fa6f | ||
|
|
e569dc2152 | ||
|
|
1aa042c5ef | ||
|
|
1450315b98 | ||
|
|
aefbb3d812 | ||
|
|
c3f12398cd | ||
|
|
2272f2a01a | ||
|
|
fb71078ea2 | ||
|
|
1c7c93191e | ||
|
|
0713f730b3 | ||
|
|
5f7a18b411 | ||
|
|
020c8d181a | ||
|
|
ab3f024031 | ||
|
|
d989c61de5 | ||
|
|
5dd8059734 | ||
|
|
b50e5511ea |
@@ -1,5 +1,4 @@
|
||||
REACT_APP_INFURA_KEY="099fc58e0de9451d80b18d7c74caa7c1"
|
||||
REACT_APP_PORTIS_ID="c0e2bf01-4b08-4fd5-ac7b-8e26b58cd236"
|
||||
REACT_APP_FORTMATIC_KEY="pk_live_F937DF033A1666BF"
|
||||
REACT_APP_GOOGLE_ANALYTICS_ID="UA-128182339-4"
|
||||
REACT_APP_GOOGLE_ANALYTICS_ID="G-KDP9B6W4H8"
|
||||
REACT_APP_FIREBASE_KEY="AIzaSyBcZWwTcTJHj_R6ipZcrJkXdq05PuX0Rs0"
|
||||
|
||||
24
.github/pull_request_template.md
vendored
Normal file
24
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
Your PR title must follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/#summary), and should start with one of the following [types](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#type):
|
||||
|
||||
- build: Changes that affect the build system or external dependencies (example scopes: yarn, eslint, typescript)
|
||||
- ci: Changes to our CI configuration files and scripts (example scopes: vercel, github, cypress)
|
||||
- docs: Documentation only changes
|
||||
- feat: A new feature
|
||||
- fix: A bug fix
|
||||
- perf: A code change that improves performance
|
||||
- refactor: A code change that neither fixes a bug nor adds a feature
|
||||
- style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
|
||||
- test: Adding missing tests or correcting existing tests
|
||||
|
||||
Example commit messages:
|
||||
|
||||
- feat: adds support for gnosis safe wallet
|
||||
- fix: removes a polling memory leak
|
||||
- chore: bumps redux version
|
||||
|
||||
Other things to note:
|
||||
|
||||
- Please describe the change using verb statements (ex: Removes X from Y)
|
||||
- PRs with multiple changes should use a list of verb statements
|
||||
- Add any relevant unit / integration tests
|
||||
- Changes will be previewable via vercel. Non-obvious changes should include instructions for how to reproduce them
|
||||
40
.github/workflows/bundle.yaml
vendored
40
.github/workflows/bundle.yaml
vendored
@@ -1,40 +0,0 @@
|
||||
name: Widgets
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- uses: actions/cache@v2
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: yarn widgets:build
|
||||
2
.github/workflows/crowdin-sync.yaml
vendored
2
.github/workflows/crowdin-sync.yaml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
run: "yarn i18n:extract"
|
||||
|
||||
- name: Synchronize
|
||||
uses: crowdin/github-action@1.1.0
|
||||
uses: crowdin/github-action@1.4.9
|
||||
with:
|
||||
upload_sources: false
|
||||
download_translations: true
|
||||
|
||||
9
.github/workflows/integration-tests.yaml
vendored
9
.github/workflows/integration-tests.yaml
vendored
@@ -37,14 +37,13 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- run: yarn cypress install
|
||||
|
||||
- run: yarn build
|
||||
env:
|
||||
CI: false
|
||||
REACT_APP_SERVICE_WORKER: false
|
||||
CI: false # disables lint checks when building
|
||||
|
||||
- run: yarn test:e2e
|
||||
- run: yarn serve &
|
||||
|
||||
- run: yarn cypress:run --record
|
||||
env:
|
||||
CYPRESS_INTEGRATION_TEST_PRIVATE_KEY: ${{ secrets.CYPRESS_INTEGRATION_TEST_PRIVATE_KEY }}
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -19,8 +19,6 @@
|
||||
|
||||
# builds
|
||||
/build
|
||||
/cosmos-export
|
||||
/dist
|
||||
/dts
|
||||
|
||||
# misc
|
||||
@@ -39,8 +37,6 @@ yarn-error.log*
|
||||
notes.txt
|
||||
.idea/
|
||||
|
||||
.vscode/
|
||||
|
||||
package-lock.json
|
||||
|
||||
cypress/videos
|
||||
|
||||
19
.vscode/settings.json
vendored
Normal file
19
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"npm.packageManager": "yarn",
|
||||
"typescript.updateImportsOnFileMove.enabled": "always",
|
||||
"javascript.updateImportsOnFileMove.enabled": "always",
|
||||
"editor.formatOnSaveMode": "file",
|
||||
"editor.tabCompletion": "on",
|
||||
"editor.tabSize": 2,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.inlineSuggest.enabled": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": true
|
||||
},
|
||||
"files.eol": "\n",
|
||||
"eslint.enable": true,
|
||||
"eslint.debug": true,
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,73 @@
|
||||
|
||||
# Contributing
|
||||
|
||||
Thank you for your interest in contributing to the Uniswap interface! 🦄
|
||||
|
||||
# Development
|
||||
|
||||
Before running anything, you'll need to install the dependencies:
|
||||
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
## Running the interface locally
|
||||
|
||||
1. `yarn install`
|
||||
1. `yarn start`
|
||||
```
|
||||
yarn start
|
||||
```
|
||||
|
||||
The interface should automatically open. If it does not, navigate to [http://localhost:3000].
|
||||
|
||||
## Creating a production build
|
||||
|
||||
1. `yarn install`
|
||||
1. `yarn build`
|
||||
```
|
||||
yarn build
|
||||
```
|
||||
|
||||
To serve the production build:
|
||||
|
||||
```
|
||||
yarn serve
|
||||
```
|
||||
|
||||
Then, navigate to [http://localhost:3000] to see it.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
```
|
||||
yarn test
|
||||
```
|
||||
|
||||
By default, this runs only unit tests that have been affected since the last commit. To run _all_ unit tests:
|
||||
|
||||
```
|
||||
yarn test --watchAll
|
||||
```
|
||||
|
||||
## Running integration tests (cypress)
|
||||
|
||||
Integration tests require a server to be running. In order to see your changes quickly, run `start` in its own tab/window:
|
||||
|
||||
```
|
||||
yarn start
|
||||
```
|
||||
|
||||
Integration tests are run using `cypress`. When developing locally, use `cypress:open` for an interactive UI, and to inspect the rendered page:
|
||||
|
||||
```
|
||||
yarn cypress:open
|
||||
```
|
||||
|
||||
To run _all_ cypress integration tests _from the command line_:
|
||||
|
||||
```
|
||||
yarn cypress:run
|
||||
```
|
||||
|
||||
## Engineering standards
|
||||
|
||||
Code merged into the `main` branch of this repository should adhere to high standards of correctness and maintainability.
|
||||
Use your best judgment when applying these standards. If code is in the critical path, will be frequently visited, or
|
||||
Code merged into the `main` branch of this repository should adhere to high standards of correctness and maintainability.
|
||||
Use your best judgment when applying these standards. If code is in the critical path, will be frequently visited, or
|
||||
makes large architectural changes, consider following all the standards.
|
||||
|
||||
- Have at least one engineer approve of large code refactorings
|
||||
@@ -39,7 +88,7 @@ The following points should help guide your development:
|
||||
- Avoid adding steps to the development/build processes
|
||||
- The build must be deterministic, i.e. a particular commit hash always produces the same build
|
||||
- Decentralization: anyone can run the interface
|
||||
- An Ethereum node should be the only critical dependency
|
||||
- An Ethereum node should be the only critical dependency
|
||||
- All other external dependencies should only enhance the UX ([graceful degradation](https://developer.mozilla.org/en-US/docs/Glossary/Graceful_degradation))
|
||||
- Accessibility: anyone can use the interface
|
||||
- The interface should be responsive, small and also run well on low performance devices (majority of swaps on mobile!)
|
||||
@@ -48,14 +97,14 @@ The following points should help guide your development:
|
||||
|
||||
Releases are cut automatically from the `main` branch Monday-Thursday in the morning according to the [release workflow](./.github/workflows/release.yaml).
|
||||
|
||||
Fix pull requests should be merged whenever ready and tested.
|
||||
Fix pull requests should be merged whenever ready and tested.
|
||||
If a fix is urgently needed in production, releases can be manually triggered on [GitHub](https://github.com/Uniswap/uniswap-interface/actions/workflows/release.yaml)
|
||||
after the fix is merged into `main`.
|
||||
|
||||
Features should not be merged into `main` until they are ready for users.
|
||||
When building larger features or collaborating with other developers, create a new branch from `main` to track its development.
|
||||
Use the automatic Vercel preview for sharing the feature to collect feedback.
|
||||
When the feature is ready for review, create a new pull request from the feature branch into `main` and request reviews from
|
||||
When the feature is ready for review, create a new pull request from the feature branch into `main` and request reviews from
|
||||
the appropriate UX reviewers (PMs or designers).
|
||||
|
||||
## Finding a first issue
|
||||
@@ -65,7 +114,7 @@ Start with issues with the label
|
||||
|
||||
# Translations
|
||||
|
||||
Uniswap uses [Crowdin](https://crowdin.com/project/uniswap-interface) for managing translations.
|
||||
Uniswap uses [Crowdin](https://crowdin.com/project/uniswap-interface) for managing translations.
|
||||
[This workflow](./.github/workflows/crowdin.yaml) uploads new strings for translation to the Crowdin project whenever code using the [lingui translation macros](https://lingui.js.org/ref/macro.html) is merged into `main`.
|
||||
|
||||
Every hour, translations are synced back down from Crowdin to the repository in [this other workflow](./.github/workflows/crowdin-sync.yaml).
|
||||
|
||||
12
README.md
12
README.md
@@ -1,9 +1,9 @@
|
||||
# Uniswap Interface
|
||||
# Uniswap Labs Interface
|
||||
|
||||
[](https://github.com/Uniswap/uniswap-interface/actions/workflows/unit-tests.yaml)
|
||||
[](https://github.com/Uniswap/uniswap-interface/actions/workflows/integration-tests.yaml)
|
||||
[](https://github.com/Uniswap/uniswap-interface/actions/workflows/lint.yml)
|
||||
[](https://github.com/Uniswap/uniswap-interface/actions/workflows/release.yaml)
|
||||
[](https://github.com/Uniswap/interface/actions/workflows/unit-tests.yaml)
|
||||
[](https://github.com/Uniswap/interface/actions/workflows/integration-tests.yaml)
|
||||
[](https://github.com/Uniswap/interface/actions/workflows/lint.yml)
|
||||
[](https://github.com/Uniswap/interface/actions/workflows/release.yaml)
|
||||
[](https://crowdin.com/project/uniswap-interface)
|
||||
|
||||
An open source interface for Uniswap -- a protocol for decentralized exchange of Ethereum tokens.
|
||||
@@ -48,4 +48,4 @@ The Uniswap Interface supports swapping, adding liquidity, removing liquidity an
|
||||
## Accessing Uniswap V1
|
||||
|
||||
The Uniswap V1 interface for mainnet and testnets is accessible via IPFS gateways
|
||||
linked from the [v1.0.0 release](https://github.com/Uniswap/uniswap-interface/releases/tag/v1.0.0).
|
||||
linked from the [v1.0.0 release](https://github.com/Uniswap/uniswap-interface/releases/tag/v1.0.0).
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"watchDirs": [
|
||||
"src"
|
||||
],
|
||||
"webpack": {
|
||||
"configPath": "react-scripts/config/webpack.config",
|
||||
"overridePath": "cosmos.override.cjs"
|
||||
},
|
||||
"port": 5001
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const { DefinePlugin } = require('webpack')
|
||||
|
||||
// Renders the cosmos fixtures in isolation, instead of using public/index.html.
|
||||
module.exports = (webpackConfig) => ({
|
||||
...webpackConfig,
|
||||
plugins: webpackConfig.plugins.map((plugin) => {
|
||||
if (plugin instanceof HtmlWebpackPlugin) {
|
||||
return new HtmlWebpackPlugin({
|
||||
templateContent: '<body></body>',
|
||||
})
|
||||
}
|
||||
if (plugin instanceof DefinePlugin) {
|
||||
return new DefinePlugin({
|
||||
...plugin.definitions,
|
||||
'process.env': {
|
||||
...plugin.definitions['process.env'],
|
||||
REACT_APP_IS_WIDGET: true,
|
||||
REACT_APP_LOCALES: '"../locales"',
|
||||
},
|
||||
})
|
||||
}
|
||||
return plugin
|
||||
}),
|
||||
})
|
||||
@@ -1,16 +0,0 @@
|
||||
// Custom test environment to provide `TextEncoder`/`TextDecoder`
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const Environment = require('jest-environment-jsdom')
|
||||
|
||||
module.exports = class CustomTestEnvironment extends Environment {
|
||||
async setup() {
|
||||
await super.setup()
|
||||
if (typeof this.global.TextEncoder === 'undefined') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { TextEncoder, TextDecoder } = require('util')
|
||||
this.global.TextEncoder = TextEncoder
|
||||
this.global.TextDecoder = TextDecoder
|
||||
}
|
||||
}
|
||||
}
|
||||
20
cypress.config.ts
Normal file
20
cypress.config.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { defineConfig } from 'cypress'
|
||||
|
||||
export default defineConfig({
|
||||
projectId: 'yp82ef',
|
||||
video: false,
|
||||
defaultCommandTimeout: 10000,
|
||||
chromeWebSecurity: false,
|
||||
e2e: {
|
||||
setupNodeEvents(on, config) {
|
||||
return {
|
||||
...config,
|
||||
// Only enable Chrome.
|
||||
// Electron (the default) has issues injecting window.ethereum before pageload, so it is not viable.
|
||||
browsers: config.browsers.filter(({ name }) => name === 'chrome'),
|
||||
}
|
||||
},
|
||||
baseUrl: 'http://localhost:3000',
|
||||
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
|
||||
},
|
||||
})
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"projectId": "yp82ef",
|
||||
"baseUrl": "http://localhost:3000",
|
||||
"pluginsFile": false,
|
||||
"supportFile": "cypress/support/index.js",
|
||||
"video": false,
|
||||
"defaultCommandTimeout": 10000
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TEST_ADDRESS_NEVER_USE_SHORTENED } from '../support/commands'
|
||||
import { TEST_ADDRESS_NEVER_USE_SHORTENED } from '../support/ethereum'
|
||||
|
||||
describe('Landing Page', () => {
|
||||
beforeEach(() => cy.visit('/'))
|
||||
@@ -1,5 +1,6 @@
|
||||
describe('Pool', () => {
|
||||
beforeEach(() => cy.visit('/pool'))
|
||||
|
||||
it('add liquidity links to /add/ETH', () => {
|
||||
cy.get('#join-pool-button').click()
|
||||
cy.url().should('contain', '/add/ETH')
|
||||
84
cypress/e2e/service-worker.test.ts
Normal file
84
cypress/e2e/service-worker.test.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import assert = require('assert')
|
||||
|
||||
describe('Service Worker', () => {
|
||||
before(() => {
|
||||
// Fail fast if there is no Service Worker on this build.
|
||||
cy.request({ url: '/service-worker.js', headers: { 'Service-Worker': 'script' } }).then((response) => {
|
||||
const isValid = isValidServiceWorker(response)
|
||||
if (!isValid) {
|
||||
throw new Error(
|
||||
'\n' +
|
||||
'Service Worker tests must be run on a production-like build\n' +
|
||||
'To test, build with `yarn build:e2e` and serve with `yarn serve`'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
function isValidServiceWorker(response: Cypress.Response<any>) {
|
||||
const contentType = response.headers['content-type']
|
||||
return !(response.status === 404 || (contentType != null && contentType.indexOf('javascript') === -1))
|
||||
}
|
||||
})
|
||||
|
||||
function unregister() {
|
||||
return cy.log('unregister service worker').then(async () => {
|
||||
const cacheKeys = await window.caches.keys()
|
||||
const cacheKey = cacheKeys.find((key) => key.match(/precache/))
|
||||
if (cacheKey) {
|
||||
await window.caches.delete(cacheKey)
|
||||
}
|
||||
|
||||
const sw = await window.navigator.serviceWorker.getRegistration(Cypress.config().baseUrl ?? undefined)
|
||||
await sw?.unregister()
|
||||
})
|
||||
}
|
||||
before(unregister)
|
||||
after(unregister)
|
||||
|
||||
beforeEach(() => {
|
||||
cy.intercept({ hostname: 'www.google-analytics.com' }, (req) => {
|
||||
const body = req.body.toString()
|
||||
if (req.query['ep.event_category'] === 'Service Worker' || body.includes('Service%20Worker')) {
|
||||
if (req.query['en'] === 'Not Installed' || body.includes('Not%20Installed')) {
|
||||
req.alias = 'NotInstalled'
|
||||
} else if (req.query['en'] === 'Cache Hit' || body.includes('Cache%20Hit')) {
|
||||
req.alias = 'CacheHit'
|
||||
} else if (req.query['en'] === 'Cache Miss' || body.includes('Cache%20Miss')) {
|
||||
req.alias = 'CacheMiss'
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('installs a ServiceWorker', () => {
|
||||
cy.visit('/', { serviceWorker: true })
|
||||
.get('#swap-page')
|
||||
.wait('@NotInstalled', { timeout: 20000 })
|
||||
.window({ timeout: 20000 })
|
||||
.and((win) => {
|
||||
expect(win.navigator.serviceWorker.controller?.state).to.equal('activated')
|
||||
})
|
||||
})
|
||||
|
||||
it('records a cache hit', () => {
|
||||
cy.visit('/', { serviceWorker: true }).get('#swap-page').wait('@CacheHit', { timeout: 20000 })
|
||||
})
|
||||
|
||||
it('records a cache miss', () => {
|
||||
cy.then(async () => {
|
||||
const cacheKeys = await window.caches.keys()
|
||||
const cacheKey = cacheKeys.find((key) => key.match(/precache/))
|
||||
assert(cacheKey)
|
||||
|
||||
const cache = await window.caches.open(cacheKey)
|
||||
const keys = await cache.keys()
|
||||
const key = keys.find((key) => key.url.match(/index/))
|
||||
assert(key)
|
||||
|
||||
await cache.put(key, new Response())
|
||||
})
|
||||
.visit('/', { serviceWorker: true })
|
||||
.get('#swap-page')
|
||||
.wait('@CacheMiss', { timeout: 20000 })
|
||||
})
|
||||
})
|
||||
@@ -3,11 +3,11 @@ describe('Swap', () => {
|
||||
cy.visit('/swap')
|
||||
})
|
||||
|
||||
it('starts with an ETH/USDC swap and quotes it', () => {
|
||||
cy.get('#swap-currency-input .token-amount-input').should('have.value', '1')
|
||||
it('starts with ETH selected by default', () => {
|
||||
cy.get('#swap-currency-input .token-amount-input').should('have.value', '')
|
||||
cy.get('#swap-currency-input .token-symbol-container').should('contain.text', 'ETH')
|
||||
cy.get('#swap-currency-output .token-amount-input').should('not.have.value', '')
|
||||
cy.get('#swap-currency-output .token-symbol-container').should('contain.text', 'USDC')
|
||||
cy.get('#swap-currency-output .token-amount-input').should('not.have.value')
|
||||
cy.get('#swap-currency-output .token-symbol-container').should('contain.text', 'Select a token')
|
||||
})
|
||||
|
||||
it('can enter an amount into input', () => {
|
||||
10
cypress/support/commands.d.ts
vendored
10
cypress/support/commands.d.ts
vendored
@@ -1,10 +0,0 @@
|
||||
export const TEST_ADDRESS_NEVER_USE: string
|
||||
|
||||
export const TEST_ADDRESS_NEVER_USE_SHORTENED: string
|
||||
|
||||
// declare namespace Cypress {
|
||||
// // eslint-disable-next-line @typescript-eslint/class-name-casing
|
||||
// interface cy {
|
||||
// additionalCommands(): void
|
||||
// }
|
||||
// }
|
||||
52
cypress/support/e2e.ts
Normal file
52
cypress/support/e2e.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
// ***********************************************************
|
||||
// This file is processed and loaded automatically before your test files.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.ts using ES2015 syntax:
|
||||
import { injected } from './ethereum'
|
||||
import assert = require('assert')
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Cypress {
|
||||
interface ApplicationWindow {
|
||||
ethereum: typeof injected
|
||||
}
|
||||
interface VisitOptions {
|
||||
serviceWorker?: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sets up the injected provider to be a mock ethereum provider with the given mnemonic/index
|
||||
// eslint-disable-next-line no-undef
|
||||
Cypress.Commands.overwrite(
|
||||
'visit',
|
||||
(original, url: string | Partial<Cypress.VisitOptions>, options?: Partial<Cypress.VisitOptions>) => {
|
||||
assert(typeof url === 'string')
|
||||
|
||||
cy.intercept('/service-worker.js', options?.serviceWorker ? undefined : { statusCode: 404 }).then(() => {
|
||||
original({
|
||||
...options,
|
||||
url: (url.startsWith('/') && url.length > 2 && !url.startsWith('/#') ? `/#${url}` : url) + '?chain=rinkeby',
|
||||
onBeforeLoad(win) {
|
||||
options?.onBeforeLoad?.(win)
|
||||
win.localStorage.clear()
|
||||
win.ethereum = injected
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
// Infura security policies are based on Origin headers.
|
||||
// These are stripped by cypress because chromeWebSecurity === false; this adds them back in.
|
||||
cy.intercept(/infura.io/, (res) => {
|
||||
res.headers['origin'] = 'http://localhost:3000'
|
||||
res.continue()
|
||||
})
|
||||
})
|
||||
@@ -1,8 +1,6 @@
|
||||
// ***********************************************
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
/**
|
||||
* Updates cy.visit() to include an injected window.ethereum provider.
|
||||
*/
|
||||
|
||||
import { Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge'
|
||||
import { JsonRpcProvider } from '@ethersproject/providers'
|
||||
@@ -20,14 +18,16 @@ export const TEST_ADDRESS_NEVER_USE_SHORTENED = `${TEST_ADDRESS_NEVER_USE.substr
|
||||
6
|
||||
)}...${TEST_ADDRESS_NEVER_USE.substr(-4, 4)}`
|
||||
|
||||
class CustomizedBridge extends Eip1193Bridge {
|
||||
const provider = new JsonRpcProvider('https://rinkeby.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847', 4)
|
||||
const signer = new Wallet(TEST_PRIVATE_KEY, provider)
|
||||
export const injected = new (class extends Eip1193Bridge {
|
||||
chainId = 4
|
||||
|
||||
async sendAsync(...args) {
|
||||
async sendAsync(...args: any[]) {
|
||||
console.debug('sendAsync called', ...args)
|
||||
return this.send(...args)
|
||||
}
|
||||
async send(...args) {
|
||||
async send(...args: any[]) {
|
||||
console.debug('send called', ...args)
|
||||
const isCallbackForm = typeof args[0] === 'object' && typeof args[1] === 'function'
|
||||
let callback
|
||||
@@ -71,19 +71,4 @@ class CustomizedBridge extends Eip1193Bridge {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sets up the injected provider to be a mock ethereum provider with the given mnemonic/index
|
||||
// eslint-disable-next-line no-undef
|
||||
Cypress.Commands.overwrite('visit', (original, url, options) => {
|
||||
return original(url.startsWith('/') && url.length > 2 && !url.startsWith('/#') ? `/#${url}` : url, {
|
||||
...options,
|
||||
onBeforeLoad(win) {
|
||||
options && options.onBeforeLoad && options.onBeforeLoad(win)
|
||||
win.localStorage.clear()
|
||||
const provider = new JsonRpcProvider('https://rinkeby.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847', 4)
|
||||
const signer = new Wallet(TEST_PRIVATE_KEY, provider)
|
||||
win.ethereum = new CustomizedBridge(signer, provider)
|
||||
},
|
||||
})
|
||||
})
|
||||
})(signer, provider)
|
||||
@@ -1,9 +0,0 @@
|
||||
// ***********************************************************
|
||||
// This file is processed and loaded automatically before your test files.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.ts using ES2015 syntax:
|
||||
import './commands'
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"baseUrl": "../node_modules",
|
||||
"target": "es5",
|
||||
"lib": ["es5", "dom"],
|
||||
"types": ["cypress"]
|
||||
|
||||
281
package.json
281
package.json
@@ -1,34 +1,62 @@
|
||||
{
|
||||
"name": "@uniswap/widgets",
|
||||
"version": "0.0.21-beta",
|
||||
"version": "1.0.7",
|
||||
"description": "Uniswap Interface",
|
||||
"homepage": ".",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"type": "module",
|
||||
"types": "dist/index.d.ts",
|
||||
"main": "dist/cjs/index.cjs",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/cjs/index.cjs"
|
||||
},
|
||||
"./locales/*": {
|
||||
"import": "./dist/locales/*.js",
|
||||
"require": "./dist/cjs/locales/*.cjs"
|
||||
},
|
||||
"./fonts.css": {
|
||||
"import": "./dist/fonts.css",
|
||||
"require": "./dist/fonts.css"
|
||||
"license": "GPL-3.0-or-later",
|
||||
"scripts": {
|
||||
"contracts:compile:abi": "typechain --target ethers-v5 --out-dir src/abis/types \"./src/abis/**/*.json\"",
|
||||
"contracts:compile:v3": "typechain --target ethers-v5 --out-dir src/types/v3 \"./node_modules/@uniswap/**/artifacts/contracts/**/*[!dbg].json\"",
|
||||
"contracts:compile": "yarn contracts:compile:abi && yarn contracts:compile:v3",
|
||||
"graphql:generate": "graphql-codegen --config codegen.yml",
|
||||
"prei18n:extract": "touch src/locales/en-US.po",
|
||||
"i18n:extract": "lingui extract --locale en-US",
|
||||
"i18n:compile": "yarn i18n:extract && lingui compile",
|
||||
"i18n:pseudo": "lingui extract --locale pseudo && lingui compile",
|
||||
"prepare": "yarn contracts:compile && yarn graphql:generate && yarn i18n:compile",
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"serve": "serve build -l 3000",
|
||||
"test": "react-scripts test --coverage",
|
||||
"cypress:open": "cypress open --browser chrome --e2e",
|
||||
"cypress:run": "cypress run --browser chrome --e2e"
|
||||
},
|
||||
"jest": {
|
||||
"collectCoverageFrom": [
|
||||
"src/components/**/*.ts*",
|
||||
"src/hooks/**/*.ts*",
|
||||
"src/lib/hooks/**/*.ts*",
|
||||
"src/lib/state/**/*.ts*",
|
||||
"src/lib/utils/**/*.ts*",
|
||||
"src/pages/**/*.ts*",
|
||||
"src/state/**/*.ts*",
|
||||
"src/utils/**/*.ts*"
|
||||
],
|
||||
"coverageThreshold": {
|
||||
"global": {
|
||||
"branches": 4,
|
||||
"functions": 6,
|
||||
"lines": 9,
|
||||
"statements": 9
|
||||
}
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-transform-runtime": "^7.17.0",
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
"@babel/preset-react": "^7.16.7",
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@ethersproject/experimental": "^5.4.0",
|
||||
"@fontsource/ibm-plex-mono": "^4.5.1",
|
||||
"@fontsource/inter": "^4.5.1",
|
||||
"@gnosis.pm/safe-apps-web3-react": "^0.6.0",
|
||||
"@graphql-codegen/cli": "1.21.5",
|
||||
"@graphql-codegen/typescript": "1.22.3",
|
||||
@@ -39,19 +67,11 @@
|
||||
"@lingui/macro": "^3.9.0",
|
||||
"@lingui/react": "^3.9.0",
|
||||
"@metamask/jazzicon": "^2.0.0",
|
||||
"@popperjs/core": "^2.4.4",
|
||||
"@reach/dialog": "^0.10.3",
|
||||
"@reach/portal": "^0.10.3",
|
||||
"@react-hook/window-scroll": "^1.3.0",
|
||||
"@rollup/plugin-alias": "^3.1.9",
|
||||
"@rollup/plugin-babel": "^5.3.0",
|
||||
"@rollup/plugin-commonjs": "^21.0.1",
|
||||
"@rollup/plugin-eslint": "^8.0.1",
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
"@rollup/plugin-node-resolve": "^13.1.3",
|
||||
"@rollup/plugin-replace": "^3.0.1",
|
||||
"@rollup/plugin-typescript": "^8.3.0",
|
||||
"@rollup/plugin-url": "^6.1.0",
|
||||
"@svgr/rollup": "^6.2.0",
|
||||
"@reduxjs/toolkit": "^1.6.1",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^12.0.0",
|
||||
"@testing-library/react-hooks": "^7.0.2",
|
||||
@@ -84,19 +104,32 @@
|
||||
"@uniswap/governance": "^1.0.2",
|
||||
"@uniswap/liquidity-staker": "^1.0.2",
|
||||
"@uniswap/merkle-distributor": "1.0.1",
|
||||
"@uniswap/redux-multicall": "^1.1.1",
|
||||
"@uniswap/router-sdk": "^1.0.3",
|
||||
"@uniswap/sdk-core": "^3.0.1",
|
||||
"@uniswap/smart-order-router": "^2.5.26",
|
||||
"@uniswap/token-lists": "^1.0.0-beta.27",
|
||||
"@uniswap/v2-core": "1.0.0",
|
||||
"@uniswap/v2-periphery": "^1.1.0-beta.0",
|
||||
"@uniswap/v2-sdk": "^3.0.1",
|
||||
"@uniswap/v3-core": "1.0.0",
|
||||
"@uniswap/v3-periphery": "^1.1.1",
|
||||
"@web3-react/metamask": "8.0.13-beta.0",
|
||||
"@web3-react/walletconnect": "8.0.18-beta.0",
|
||||
"@uniswap/v3-sdk": "^3.8.2",
|
||||
"@web3-react/core": "^8.0.23-beta.0",
|
||||
"@web3-react/eip1193": "^8.0.18-beta.0",
|
||||
"@web3-react/empty": "^8.0.12-beta.0",
|
||||
"@web3-react/metamask": "^8.0.19-beta.0",
|
||||
"@web3-react/types": "^8.0.12-beta.0",
|
||||
"@web3-react/url": "^8.0.17-beta.0",
|
||||
"@web3-react/walletconnect": "^8.0.26-beta.0",
|
||||
"ajv": "^6.12.3",
|
||||
"array.prototype.flat": "^1.2.4",
|
||||
"array.prototype.flatmap": "^1.2.4",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"cids": "^1.0.0",
|
||||
"copy-to-clipboard": "^3.2.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"cypress": "^7.7.0",
|
||||
"cypress": "^10.1.0",
|
||||
"d3": "^7.0.0",
|
||||
"env-cmd": "^10.1.0",
|
||||
"eslint": "^7.11.0",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-plugin-better-styled-components": "^1.1.2",
|
||||
@@ -105,127 +138,13 @@
|
||||
"eslint-plugin-react-hooks": "^4.0.0",
|
||||
"eslint-plugin-simple-import-sort": "^7.0.0",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"ethers": "^5.1.4",
|
||||
"firebase": "^9.1.3",
|
||||
"graphql": "^15.5.0",
|
||||
"graphql-request": "^3.4.0",
|
||||
"immer": "^9.0.6",
|
||||
"inter-ui": "^3.13.1",
|
||||
"jest-styled-components": "^7.0.5",
|
||||
"polyfill-object.fromentries": "^1.0.1",
|
||||
"prettier": "^2.2.1",
|
||||
"qs": "^6.9.4",
|
||||
"react": "^17.0.1",
|
||||
"react-confetti": "^6.0.0",
|
||||
"react-cosmos": "^5.6.6",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-ga": "^2.5.7",
|
||||
"react-is": "^17.0.2",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-router-dom": "^5.0.0",
|
||||
"react-scripts": "^4.0.3",
|
||||
"react-spring": "^8.0.27",
|
||||
"react-use-gesture": "^6.0.14",
|
||||
"redux": "^4.1.2",
|
||||
"redux-localstorage-simple": "^2.3.1",
|
||||
"rollup": "^2.63.0",
|
||||
"rollup-plugin-copy": "^3.4.0",
|
||||
"rollup-plugin-delete": "^2.0.0",
|
||||
"rollup-plugin-dts": "^4.1.0",
|
||||
"rollup-plugin-multi-input": "^1.3.1",
|
||||
"rollup-plugin-node-externals": "^3.1.2",
|
||||
"rollup-plugin-scss": "^3.0.0",
|
||||
"rollup-plugin-typescript2": "^0.31.1",
|
||||
"sass": "^1.45.1",
|
||||
"serve": "^11.3.2",
|
||||
"start-server-and-test": "^1.11.0",
|
||||
"typechain": "^5.0.0",
|
||||
"typescript": "^4.4.3",
|
||||
"ua-parser-js": "^0.7.28",
|
||||
"use-count-up": "^2.2.5",
|
||||
"use-resize-observer": "^8.0.0",
|
||||
"wcag-contrast": "^3.0.0",
|
||||
"web-vitals": "^2.1.0",
|
||||
"web3-react-abstract-connector": "npm:@web3-react/abstract-connector@^6.0.7",
|
||||
"web3-react-fortmatic-connector": "npm:@web3-react/fortmatic-connector@^6.0.9",
|
||||
"web3-react-injected-connector": "npm:@web3-react/injected-connector@^6.0.7",
|
||||
"web3-react-portis-connector": "npm:@web3-react/portis-connector@^6.0.9",
|
||||
"web3-react-types": "npm:@web3-react/types@^6.0.7",
|
||||
"web3-react-walletconnect-connector": "npm:@web3-react/walletconnect-connector@^7.0.2-alpha.0",
|
||||
"web3-react-walletlink-connector": "npm:@web3-react/walletlink-connector@^6.2.13",
|
||||
"workbox-core": "^6.1.0",
|
||||
"workbox-precaching": "^6.1.0",
|
||||
"workbox-routing": "^6.1.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"@walletconnect/ethereum-provider": "1.7.1"
|
||||
},
|
||||
"scripts": {
|
||||
"contracts:compile:abi": "typechain --target ethers-v5 --out-dir src/abis/types \"./src/abis/**/*.json\"",
|
||||
"contracts:compile:v3": "typechain --target ethers-v5 --out-dir src/types/v3 \"./node_modules/@uniswap/**/artifacts/contracts/**/*.json\"",
|
||||
"contracts:compile": "yarn contracts:compile:abi && yarn contracts:compile:v3",
|
||||
"graphql:generate": "graphql-codegen --config codegen.yml",
|
||||
"prei18n:extract": "touch src/locales/en-US.po",
|
||||
"i18n:extract": "lingui extract --locale en-US",
|
||||
"i18n:compile": "yarn i18n:extract && lingui compile",
|
||||
"i18n:pseudo": "lingui extract --locale pseudo && lingui compile",
|
||||
"prepare": "yarn contracts:compile && yarn graphql:generate && yarn i18n:compile",
|
||||
"prepublishOnly": "yarn widgets:build",
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test --env=./custom-test-env.cjs",
|
||||
"test:e2e": "start-server-and-test 'serve build -l 3000' http://localhost:3000 'cypress run --record'",
|
||||
"widgets:start": "cosmos",
|
||||
"widgets:build": "rollup --config --failAfterWarnings --configPlugin typescript2"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.17.0",
|
||||
"@ethersproject/abi": "^5.4.1",
|
||||
"@ethersproject/abstract-provider": "^5.4.1",
|
||||
"@ethersproject/abstract-signer": "^5.4.1",
|
||||
"@ethersproject/address": "^5.4.0",
|
||||
"@ethersproject/bignumber": "^5.4.2",
|
||||
"@ethersproject/bytes": "^5.4.0",
|
||||
"@ethersproject/constants": "^5.4.0",
|
||||
"@ethersproject/contracts": "^5.4.1",
|
||||
"@ethersproject/experimental": "^5.4.0",
|
||||
"@ethersproject/hash": "^5.4.0",
|
||||
"@ethersproject/providers": "^5.4.0",
|
||||
"@ethersproject/solidity": "^5.4.0",
|
||||
"@ethersproject/strings": "^5.4.0",
|
||||
"@ethersproject/units": "^5.4.0",
|
||||
"@ethersproject/wallet": "^5.4.0",
|
||||
"@fontsource/ibm-plex-mono": "^4.5.1",
|
||||
"@fontsource/inter": "^4.5.1",
|
||||
"@popperjs/core": "^2.4.4",
|
||||
"@reduxjs/toolkit": "^1.6.1",
|
||||
"@uniswap/redux-multicall": "^1.0.0",
|
||||
"@uniswap/router-sdk": "^1.0.3",
|
||||
"@uniswap/sdk-core": "^3.0.1",
|
||||
"@uniswap/smart-order-router": "^2.5.10",
|
||||
"@uniswap/token-lists": "^1.0.0-beta.27",
|
||||
"@uniswap/v2-sdk": "^3.0.1",
|
||||
"@uniswap/v3-sdk": "^3.8.2",
|
||||
"@web3-react/core": "8.0.17-beta.0",
|
||||
"@web3-react/eip1193": "8.0.12-beta.0",
|
||||
"@web3-react/empty": "8.0.10-beta.0",
|
||||
"@web3-react/types": "8.0.10-beta.0",
|
||||
"@web3-react/url": "8.0.12-beta.0",
|
||||
"ajv": "^6.12.3",
|
||||
"cids": "^1.0.0",
|
||||
"immer": "^9.0.6",
|
||||
"jotai": "^1.3.7",
|
||||
"jsbi": "^3.1.4",
|
||||
"make-plural": "^7.0.0",
|
||||
@@ -234,29 +153,55 @@
|
||||
"multihashes": "^4.0.2",
|
||||
"node-vibrant": "^3.2.1-alpha.1",
|
||||
"polished": "^3.3.2",
|
||||
"polyfill-object.fromentries": "^1.0.1",
|
||||
"popper-max-size-modifier": "^0.2.0",
|
||||
"prettier": "^2.2.1",
|
||||
"qs": "^6.9.4",
|
||||
"react": "^17.0.1",
|
||||
"react-confetti": "^6.0.0",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-feather": "^2.0.8",
|
||||
"react-ga4": "^1.4.1",
|
||||
"react-is": "^17.0.2",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-popper": "^2.2.3",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-router-dom": "^5.0.0",
|
||||
"react-scripts": "^4.0.3",
|
||||
"react-spring": "^8.0.27",
|
||||
"react-use-gesture": "^6.0.14",
|
||||
"react-virtualized-auto-sizer": "^1.0.2",
|
||||
"react-window": "^1.8.5",
|
||||
"rebass": "^4.0.7",
|
||||
"redux": "^4.1.2",
|
||||
"redux-localstorage-simple": "^2.3.1",
|
||||
"sass": "^1.45.1",
|
||||
"serve": "^11.3.2",
|
||||
"setimmediate": "^1.0.5",
|
||||
"start-server-and-test": "^1.11.0",
|
||||
"styled-components": "^5.3.0",
|
||||
"tiny-invariant": "^1.2.0",
|
||||
"typechain": "^5.0.0",
|
||||
"typescript": "^4.4.3",
|
||||
"ua-parser-js": "^0.7.28",
|
||||
"use-count-up": "^2.2.5",
|
||||
"use-resize-observer": "^8.0.0",
|
||||
"wcag-contrast": "^3.0.0",
|
||||
"web-vitals": "^2.1.0",
|
||||
"web3-react-abstract-connector": "npm:@web3-react/abstract-connector@^6.0.7",
|
||||
"web3-react-core": "npm:@web3-react/core@^6.0.9",
|
||||
"wicg-inert": "^3.1.1"
|
||||
"web3-react-fortmatic-connector": "npm:@web3-react/fortmatic-connector@^6.0.9",
|
||||
"web3-react-injected-connector": "npm:@web3-react/injected-connector@^6.0.7",
|
||||
"web3-react-types": "npm:@web3-react/types@^6.0.7",
|
||||
"web3-react-walletconnect-connector": "npm:@web3-react/walletconnect-connector@^7.0.2-alpha.0",
|
||||
"web3-react-walletlink-connector": "npm:@web3-react/walletlink-connector@^6.2.13",
|
||||
"wicg-inert": "^3.1.1",
|
||||
"workbox-core": "^6.1.0",
|
||||
"workbox-navigation-preload": "^6.1.0",
|
||||
"workbox-precaching": "^6.1.0",
|
||||
"workbox-routing": "^6.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": "^7.17.0",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-redux": "^7.2.2",
|
||||
"redux": "^4.1.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bufferutil": "^4.0.6",
|
||||
"encoding": "^0.1.13",
|
||||
"utf-8-validate": "^5.0.8"
|
||||
"resolutions": {
|
||||
"@walletconnect/ethereum-provider": "1.7.1"
|
||||
}
|
||||
}
|
||||
|
||||
176
rollup.config.ts
176
rollup.config.ts
@@ -1,176 +0,0 @@
|
||||
/**
|
||||
* Bundles the widgets library, which is released independently of the interface application.
|
||||
* This library lives in src/lib, but shares code with the interface application.
|
||||
*/
|
||||
|
||||
import alias from '@rollup/plugin-alias'
|
||||
import babel from '@rollup/plugin-babel'
|
||||
import commonjs from '@rollup/plugin-commonjs'
|
||||
import json from '@rollup/plugin-json'
|
||||
import resolve from '@rollup/plugin-node-resolve'
|
||||
import replace from '@rollup/plugin-replace'
|
||||
import typescript from '@rollup/plugin-typescript'
|
||||
import url from '@rollup/plugin-url'
|
||||
import svgr from '@svgr/rollup'
|
||||
import path from 'path'
|
||||
import { RollupWarning } from 'rollup'
|
||||
import copy from 'rollup-plugin-copy'
|
||||
import del from 'rollup-plugin-delete'
|
||||
import dts from 'rollup-plugin-dts'
|
||||
// @ts-ignore // missing types
|
||||
import multi from 'rollup-plugin-multi-input'
|
||||
import externals from 'rollup-plugin-node-externals'
|
||||
import sass from 'rollup-plugin-scss'
|
||||
import { CompilerOptions } from 'typescript'
|
||||
|
||||
const REPLACEMENTS = {
|
||||
'process.env.REACT_APP_IS_WIDGET': true,
|
||||
'process.env.REACT_APP_LOCALES': '"./locales"',
|
||||
// esm requires fully-specified paths:
|
||||
'react/jsx-runtime': 'react/jsx-runtime.js',
|
||||
}
|
||||
|
||||
const EXTENSIONS = ['.js', '.jsx', '.ts', '.tsx']
|
||||
const ASSET_EXTENSIONS = ['.png', '.svg']
|
||||
function isAsset(source: string) {
|
||||
const extname = path.extname(source)
|
||||
return extname && [...ASSET_EXTENSIONS, '.css', '.scss'].includes(extname)
|
||||
}
|
||||
|
||||
const TS_CONFIG = './tsconfig.lib.json'
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { baseUrl, paths }: CompilerOptions = require(TS_CONFIG).compilerOptions
|
||||
const aliases = Object.entries({ ...paths }).flatMap(([find, replacements]) => {
|
||||
return replacements.map((replacement) => ({
|
||||
find: path.dirname(find),
|
||||
replacement: path.join(__dirname, baseUrl || '.', path.dirname(replacement)),
|
||||
}))
|
||||
})
|
||||
|
||||
const plugins = [
|
||||
// Dependency resolution
|
||||
resolve({ extensions: EXTENSIONS }), // resolves third-party modules within node_modules/
|
||||
alias({ entries: aliases }), // resolves paths aliased through the tsconfig (babel does not use tsconfig path resolution)
|
||||
|
||||
// Source code transformation
|
||||
replace({ ...REPLACEMENTS, preventAssignment: true }),
|
||||
json(), // imports json as ES6; doing so enables type-checking and module resolution
|
||||
]
|
||||
|
||||
const check = {
|
||||
input: 'src/lib/index.tsx',
|
||||
output: { file: 'dist/widgets.tsc', inlineDynamicImports: true },
|
||||
external: isAsset,
|
||||
plugins: [
|
||||
externals({ exclude: ['constants'], deps: true, peerDeps: true }), // marks builtins, dependencies, and peerDependencies external
|
||||
...plugins,
|
||||
typescript({ tsconfig: TS_CONFIG }),
|
||||
],
|
||||
onwarn: squelchTranspilationWarnings, // this pipeline is only for typechecking and generating definitions
|
||||
}
|
||||
|
||||
const type = {
|
||||
input: 'dist/dts/lib/index.d.ts',
|
||||
output: { file: 'dist/index.d.ts' },
|
||||
external: isAsset,
|
||||
plugins: [
|
||||
externals({ exclude: ['constants'], deps: true, peerDeps: true }),
|
||||
dts({ compilerOptions: { baseUrl: 'dist/dts' } }),
|
||||
process.env.ROLLUP_WATCH ? undefined : del({ hook: 'buildEnd', targets: ['dist/widgets.tsc', 'dist/dts'] }),
|
||||
],
|
||||
}
|
||||
|
||||
/**
|
||||
* This exports scheme works for nextjs and for CRA5.
|
||||
*
|
||||
* It will also work for CRA4 if you use direct imports:
|
||||
* instead of `import { SwapWidget } from '@uniswap/widgets'`,
|
||||
* `import { SwapWidget } from '@uniswap/widgets/dist/index.js'`.
|
||||
* I do not know why CRA4 does not seem to use exports for resolution.
|
||||
*
|
||||
* Note that chunks are enabled. This is so the tokenlist spec can be loaded async,
|
||||
* to improve first load time (due to ajv). Locales are also in separate chunks.
|
||||
*
|
||||
* Lastly, note that JSON and lingui are bundled into the library, as neither are fully
|
||||
* supported/compatible with ES Modules. Both _could_ be bundled only with esm, but this
|
||||
* yields a less complex pipeline.
|
||||
*/
|
||||
|
||||
const transpile = {
|
||||
input: 'src/lib/index.tsx',
|
||||
output: [
|
||||
{
|
||||
dir: 'dist',
|
||||
format: 'esm',
|
||||
sourcemap: false,
|
||||
},
|
||||
{
|
||||
dir: 'dist/cjs',
|
||||
entryFileNames: '[name].cjs',
|
||||
chunkFileNames: '[name]-[hash].cjs',
|
||||
format: 'cjs',
|
||||
sourcemap: false,
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
externals({
|
||||
exclude: [
|
||||
'constants',
|
||||
/@lingui\/(core|react)/, // @lingui incorrectly exports esm, so it must be bundled in
|
||||
/\.json$/, // esm does not support JSON loading, so it must be bundled in
|
||||
],
|
||||
deps: true,
|
||||
peerDeps: true,
|
||||
}),
|
||||
...plugins,
|
||||
|
||||
// Source code transformation
|
||||
url({ include: ASSET_EXTENSIONS.map((extname) => '**/*' + extname), limit: Infinity }), // imports assets as data URIs
|
||||
svgr({ exportType: 'named', svgo: false }), // imports svgs as React components
|
||||
sass({ output: 'dist/fonts.css' }), // generates fonts.css
|
||||
commonjs(), // transforms cjs dependencies into tree-shakeable ES modules
|
||||
|
||||
babel({
|
||||
babelHelpers: 'runtime',
|
||||
presets: ['@babel/preset-env', ['@babel/preset-react', { runtime: 'automatic' }], '@babel/preset-typescript'],
|
||||
extensions: EXTENSIONS,
|
||||
plugins: [
|
||||
'macros', // enables @lingui and styled-components macros
|
||||
'@babel/plugin-transform-runtime', // embeds the babel runtime for library distribution
|
||||
],
|
||||
}),
|
||||
],
|
||||
onwarn: squelchTypeWarnings, // this pipeline is only for transpilation
|
||||
}
|
||||
|
||||
const locales = {
|
||||
input: 'src/locales/*.js',
|
||||
output: [
|
||||
{
|
||||
dir: 'dist',
|
||||
format: 'esm',
|
||||
sourcemap: false,
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
copy({
|
||||
copyOnce: true,
|
||||
targets: [{ src: 'src/locales/*.js', dest: 'dist/cjs/locales', rename: (name) => `${name}.cjs` }],
|
||||
}),
|
||||
commonjs(),
|
||||
multi(),
|
||||
],
|
||||
}
|
||||
|
||||
const config = [check, type, transpile, locales]
|
||||
export default config
|
||||
|
||||
function squelchTranspilationWarnings(warning: RollupWarning, warn: (warning: RollupWarning) => void) {
|
||||
if (warning.pluginCode === 'TS5055') return
|
||||
warn(warning)
|
||||
}
|
||||
|
||||
function squelchTypeWarnings(warning: RollupWarning, warn: (warning: RollupWarning) => void) {
|
||||
if (warning.code === 'UNUSED_EXTERNAL_IMPORT') return
|
||||
warn(warning)
|
||||
}
|
||||
BIN
src/assets/images/tally.png
Normal file
BIN
src/assets/images/tally.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
src/assets/images/widget-screenshot.png
Normal file
BIN
src/assets/images/widget-screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 106 KiB |
@@ -1,10 +0,0 @@
|
||||
<svg width="23" height="20" viewBox="0 0 23 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="gradient1" x1="0" y1="0" x2="1" y2="0" gradientTransform="rotate(95)">
|
||||
<stop id="stop1" offset="0" stop-color="#2274E2"/>
|
||||
<stop id="stop1" offset="0.5" stop-color="#2274E2"/>
|
||||
<stop id="stop2" offset="1" stop-color="#3FB672" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M16 16C10 16 9 10 5 10M16 16C16 17.6569 17.3431 19 19 19C20.6569 19 22 17.6569 22 16C22 14.3431 20.6569 13 19 13C17.3431 13 16 14.3431 16 16ZM5 10C9 10 10 4 16 4M5 10H1.5M16 4C16 5.65685 17.3431 7 19 7C20.6569 7 22 5.65685 22 4C22 2.34315 20.6569 1 19 1C17.3431 1 16 2.34315 16 4Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke="url(#gradient1)" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 782 B |
@@ -1,3 +1,3 @@
|
||||
<svg width="100%" height="35" viewBox="800 0 300 200" xmlns="http://www.w3.org/2000/svg">
|
||||
<line x1="0" x2="2000" y1="100" y2="100" stroke="currentColor" stroke-width="20" stroke-linecap="round" stroke-dasharray="1, 45"/>
|
||||
<svg width="100%" height="35" viewBox="850 0 300 200" xmlns="http://www.w3.org/2000/svg">
|
||||
<line x1="0" x2="3000" y1="100" y2="100" stroke="currentColor" stroke-width="20" stroke-linecap="round" stroke-dasharray="1, 45"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 233 B After Width: | Height: | Size: 233 B |
@@ -1,49 +1,64 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import React from 'react'
|
||||
import useCopyClipboard from 'hooks/useCopyClipboard'
|
||||
import React, { useCallback } from 'react'
|
||||
import { CheckCircle, Copy } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import useCopyClipboard from '../../hooks/useCopyClipboard'
|
||||
import { LinkStyledButton } from '../../theme'
|
||||
import { LinkStyledButton } from 'theme'
|
||||
|
||||
const CopyIcon = styled(LinkStyledButton)`
|
||||
color: ${({ theme }) => theme.text3};
|
||||
color: ${({ color, theme }) => color || theme.text3};
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
text-decoration: none;
|
||||
font-size: 0.825rem;
|
||||
:hover,
|
||||
:active,
|
||||
:focus {
|
||||
text-decoration: none;
|
||||
color: ${({ theme }) => theme.text2};
|
||||
color: ${({ color, theme }) => color || theme.text2};
|
||||
}
|
||||
`
|
||||
const TransactionStatusText = styled.span`
|
||||
const StyledText = styled.span`
|
||||
margin-left: 0.25rem;
|
||||
font-size: 0.825rem;
|
||||
${({ theme }) => theme.flexRowNoWrap};
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
export default function CopyHelper(props: { toCopy: string; children?: React.ReactNode }) {
|
||||
const Copied = ({ iconSize }: { iconSize?: number }) => (
|
||||
<StyledText>
|
||||
<CheckCircle size={iconSize ?? '16'} />
|
||||
<StyledText>
|
||||
<Trans>Copied</Trans>
|
||||
</StyledText>
|
||||
</StyledText>
|
||||
)
|
||||
|
||||
const Icon = ({ iconSize }: { iconSize?: number }) => (
|
||||
<StyledText>
|
||||
<Copy size={iconSize ?? '16'} />
|
||||
</StyledText>
|
||||
)
|
||||
|
||||
interface BaseProps {
|
||||
toCopy: string
|
||||
color?: string
|
||||
iconSize?: number
|
||||
iconPosition?: 'left' | 'right'
|
||||
}
|
||||
export type CopyHelperProps = BaseProps & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, keyof BaseProps>
|
||||
|
||||
export default function CopyHelper({ color, toCopy, children, iconSize, iconPosition }: CopyHelperProps) {
|
||||
const [isCopied, setCopied] = useCopyClipboard()
|
||||
const copy = useCallback(() => {
|
||||
setCopied(toCopy)
|
||||
}, [toCopy, setCopied])
|
||||
|
||||
return (
|
||||
<CopyIcon onClick={() => setCopied(props.toCopy)}>
|
||||
{isCopied ? (
|
||||
<TransactionStatusText>
|
||||
<CheckCircle size={'16'} />
|
||||
<TransactionStatusText>
|
||||
<Trans>Copied</Trans>
|
||||
</TransactionStatusText>
|
||||
</TransactionStatusText>
|
||||
) : (
|
||||
<TransactionStatusText>
|
||||
<Copy size={'16'} />
|
||||
</TransactionStatusText>
|
||||
)}
|
||||
{isCopied ? '' : props.children}
|
||||
<CopyIcon onClick={copy} color={color}>
|
||||
{iconPosition === 'left' ? isCopied ? <Copied iconSize={iconSize} /> : <Icon iconSize={iconSize} /> : null}
|
||||
{iconPosition === 'left' && <> </>}
|
||||
{isCopied ? '' : children}
|
||||
{iconPosition === 'right' && <> </>}
|
||||
{iconPosition === 'right' ? isCopied ? <Copied iconSize={iconSize} /> : <Icon iconSize={iconSize} /> : null}
|
||||
</CopyIcon>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ import {
|
||||
DepositLiquidityStakingTransactionInfo,
|
||||
ExactInputSwapTransactionInfo,
|
||||
ExactOutputSwapTransactionInfo,
|
||||
ExecuteTransactionInfo,
|
||||
MigrateV2LiquidityToV3TransactionInfo,
|
||||
QueueTransactionInfo,
|
||||
RemoveLiquidityV3TransactionInfo,
|
||||
SubmitProposalTransactionInfo,
|
||||
TransactionInfo,
|
||||
@@ -25,7 +27,7 @@ import {
|
||||
VoteTransactionInfo,
|
||||
WithdrawLiquidityStakingTransactionInfo,
|
||||
WrapTransactionInfo,
|
||||
} from '../../state/transactions/actions'
|
||||
} from '../../state/transactions/types'
|
||||
|
||||
function formatAmount(amountRaw: string, decimals: number, sigFigs: number): string {
|
||||
return new Fraction(amountRaw, JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(decimals))).toSignificant(sigFigs)
|
||||
@@ -126,6 +128,16 @@ function VoteSummary({ info }: { info: VoteTransactionInfo }) {
|
||||
}
|
||||
}
|
||||
|
||||
function QueueSummary({ info }: { info: QueueTransactionInfo }) {
|
||||
const proposalKey = `${info.governorAddress}/${info.proposalId}`
|
||||
return <Trans>Queue proposal {proposalKey}.</Trans>
|
||||
}
|
||||
|
||||
function ExecuteSummary({ info }: { info: ExecuteTransactionInfo }) {
|
||||
const proposalKey = `${info.governorAddress}/${info.proposalId}`
|
||||
return <Trans>Execute proposal {proposalKey}.</Trans>
|
||||
}
|
||||
|
||||
function DelegateSummary({ info: { delegatee } }: { info: DelegateTransactionInfo }) {
|
||||
const { ENSName } = useENSName(delegatee)
|
||||
return <Trans>Delegate voting power to {ENSName ?? delegatee}</Trans>
|
||||
@@ -339,6 +351,12 @@ export function TransactionSummary({ info }: { info: TransactionInfo }) {
|
||||
case TransactionType.REMOVE_LIQUIDITY_V3:
|
||||
return <RemoveLiquidityV3Summary info={info} />
|
||||
|
||||
case TransactionType.QUEUE:
|
||||
return <QueueSummary info={info} />
|
||||
|
||||
case TransactionType.EXECUTE:
|
||||
return <ExecuteSummary info={info} />
|
||||
|
||||
case TransactionType.SUBMIT_PROPOSAL:
|
||||
return <SubmitProposalTransactionSummary info={info} />
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Connector } from '@web3-react/types'
|
||||
import CopyHelper from 'components/AccountDetails/Copy'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useCallback, useContext } from 'react'
|
||||
import { ExternalLink as LinkIcon } from 'react-feather'
|
||||
@@ -8,16 +9,15 @@ import styled, { ThemeContext } from 'styled-components/macro'
|
||||
import { AbstractConnector } from 'web3-react-abstract-connector'
|
||||
|
||||
import { ReactComponent as Close } from '../../assets/images/x.svg'
|
||||
import { injected, portis, walletlink } from '../../connectors'
|
||||
import { injected, walletlink } from '../../connectors'
|
||||
import { SUPPORTED_WALLETS } from '../../constants/wallet'
|
||||
import { clearAllTransactions } from '../../state/transactions/actions'
|
||||
import { clearAllTransactions } from '../../state/transactions/reducer'
|
||||
import { ExternalLink, LinkStyledButton, ThemedText } from '../../theme'
|
||||
import { shortenAddress } from '../../utils'
|
||||
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
|
||||
import { ButtonSecondary } from '../Button'
|
||||
import StatusIcon from '../Identicon/StatusIcon'
|
||||
import { AutoRow } from '../Row'
|
||||
import Copy from './Copy'
|
||||
import Transaction from './Transaction'
|
||||
|
||||
const HeaderRow = styled.div`
|
||||
@@ -181,15 +181,6 @@ function WrappedStatusIcon({ connector }: { connector: AbstractConnector | Conne
|
||||
return (
|
||||
<IconWrapper size={16}>
|
||||
<StatusIcon connector={connector} />
|
||||
{connector === portis && (
|
||||
<MainWalletAction
|
||||
onClick={() => {
|
||||
portis.portis.showPortis()
|
||||
}}
|
||||
>
|
||||
<Trans>Show Portis</Trans>
|
||||
</MainWalletAction>
|
||||
)}
|
||||
</IconWrapper>
|
||||
)
|
||||
}
|
||||
@@ -210,10 +201,6 @@ const WalletAction = styled(ButtonSecondary)`
|
||||
}
|
||||
`
|
||||
|
||||
const MainWalletAction = styled(WalletAction)`
|
||||
color: ${({ theme }) => theme.primary1};
|
||||
`
|
||||
|
||||
function renderTransactions(transactions: string[]) {
|
||||
return (
|
||||
<TransactionListWrapper>
|
||||
@@ -323,11 +310,11 @@ export default function AccountDetails({
|
||||
<AccountControl>
|
||||
<div>
|
||||
{account && (
|
||||
<Copy toCopy={account}>
|
||||
<CopyHelper toCopy={account} iconPosition="left">
|
||||
<span style={{ marginLeft: '4px' }}>
|
||||
<Trans>Copy Address</Trans>
|
||||
</span>
|
||||
</Copy>
|
||||
</CopyHelper>
|
||||
)}
|
||||
{chainId && account && (
|
||||
<AddressLink
|
||||
@@ -349,11 +336,11 @@ export default function AccountDetails({
|
||||
<AccountControl>
|
||||
<div>
|
||||
{account && (
|
||||
<Copy toCopy={account}>
|
||||
<CopyHelper toCopy={account} iconPosition="left">
|
||||
<span style={{ marginLeft: '4px' }}>
|
||||
<Trans>Copy Address</Trans>
|
||||
</span>
|
||||
</Copy>
|
||||
</CopyHelper>
|
||||
)}
|
||||
{chainId && account && (
|
||||
<AddressLink
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Trans } from '@lingui/macro'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { ReactNode, useMemo } from 'react'
|
||||
|
||||
// SDN OFAC addresses
|
||||
const BLOCKED_ADDRESSES: string[] = [
|
||||
'0x7Db418b5D567A4e0E8c59Ad71BE1FcE48f3E6107',
|
||||
'0x72a5843cc08275C8171E582972Aa4fDa8C397B2A',
|
||||
@@ -24,15 +23,18 @@ const BLOCKED_ADDRESSES: string[] = [
|
||||
'0x6acdfba02d390b97ac2b2d42a63e85293bcc1',
|
||||
'0x48549a34ae37b12f6a30566245176994e17c6',
|
||||
'0x5512d943ed1f7c8a43f3435c85f7ab68b30121',
|
||||
'0xc455f7fd3e0e12afd51fba5c106909934d8a0e',
|
||||
'0x3cbded43efdaf0fc77b9c55f6fc9988fcc9b757d',
|
||||
'0xC455f7fd3e0e12afd51fba5c106909934D8A0e4a',
|
||||
'0x3CBdeD43EFdAf0FC77b9C55F6fC9988fCC9b757d',
|
||||
'0x67d40EE1A85bf4a4Bb7Ffae16De985e8427B6b45',
|
||||
'0x6f1ca141a28907f78ebaa64fb83a9088b02a8352',
|
||||
'0x6acdfba02d390b97ac2b2d42a63e85293bcc160e',
|
||||
'0x6F1cA141A28907F78Ebaa64fb83A9088b02A8352',
|
||||
'0x6aCDFBA02D390b97Ac2b2d42A63E85293BCc160e',
|
||||
'0x48549a34ae37b12f6a30566245176994e17c6b4a',
|
||||
'0x5512d943ed1f7c8a43f3435c85f7ab68b30121b0',
|
||||
'0xc455f7fd3e0e12afd51fba5c106909934d8a0e4a',
|
||||
'0xC455f7fd3e0e12afd51fba5c106909934D8A0e4a',
|
||||
'0x629e7Da20197a5429d30da36E77d06CdF796b71A',
|
||||
'0x7FF9cFad3877F21d41Da833E2F775dB0569eE3D9',
|
||||
'0x098B716B8Aaf21512996dC57EB0615e2383E2f96',
|
||||
'0xfEC8A60023265364D066a1212fDE3930F6Ae8da7',
|
||||
]
|
||||
|
||||
export default function Blocklist({ children }: { children: ReactNode }) {
|
||||
|
||||
58
src/components/ConnectedAccountBlocked/index.tsx
Normal file
58
src/components/ConnectedAccountBlocked/index.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import CopyHelper from 'components/AccountDetails/Copy'
|
||||
import Column from 'components/Column'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import { AlertOctagon } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, ThemedText } from 'theme'
|
||||
|
||||
import Modal from '../Modal'
|
||||
|
||||
const ContentWrapper = styled(Column)`
|
||||
align-items: center;
|
||||
margin: 32px;
|
||||
text-align: center;
|
||||
`
|
||||
const WarningIcon = styled(AlertOctagon)`
|
||||
min-height: 22px;
|
||||
min-width: 22px;
|
||||
color: ${({ theme }) => theme.warning};
|
||||
`
|
||||
const Copy = styled(CopyHelper)`
|
||||
font-size: 12px;
|
||||
`
|
||||
|
||||
interface ConnectedAccountBlockedProps {
|
||||
account: string | null | undefined
|
||||
isOpen: boolean
|
||||
}
|
||||
|
||||
export default function ConnectedAccountBlocked(props: ConnectedAccountBlockedProps) {
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<Modal isOpen={props.isOpen} onDismiss={Function.prototype()}>
|
||||
<ContentWrapper>
|
||||
<WarningIcon />
|
||||
<ThemedText.LargeHeader lineHeight={2} marginBottom={1} marginTop={1}>
|
||||
<Trans>Blocked Address</Trans>
|
||||
</ThemedText.LargeHeader>
|
||||
<ThemedText.DarkGray fontSize={12} marginBottom={12}>
|
||||
{props.account}
|
||||
</ThemedText.DarkGray>
|
||||
<ThemedText.Main fontSize={14} marginBottom={12}>
|
||||
<Trans>This address is blocked on the Uniswap Labs interface because it is associated with one or more</Trans>{' '}
|
||||
<ExternalLink href="https://help.uniswap.org/en/articles/6149816">
|
||||
<Trans>blocked activities</Trans>
|
||||
</ExternalLink>
|
||||
.
|
||||
</ThemedText.Main>
|
||||
<ThemedText.Main fontSize={12}>
|
||||
<Trans>If you believe this is an error, please send an email including your address to </Trans>{' '}
|
||||
</ThemedText.Main>
|
||||
<Copy iconSize={12} toCopy="compliance@uniswap.org" color={theme.primary1} iconPosition="right">
|
||||
compliance@uniswap.org
|
||||
</Copy>
|
||||
</ContentWrapper>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -27,13 +27,16 @@ export function FiatValue({
|
||||
return theme.red1
|
||||
}, [priceImpact, theme.green1, theme.red1, theme.text3, theme.yellow1])
|
||||
|
||||
const p = Number(fiatValue?.toFixed())
|
||||
const visibleDecimalPlaces = p < 1.05 ? 4 : 2
|
||||
|
||||
return (
|
||||
<ThemedText.Body fontSize={14} color={fiatValue ? theme.text3 : theme.text4}>
|
||||
{fiatValue ? (
|
||||
<Trans>
|
||||
$
|
||||
<HoverInlineText
|
||||
text={fiatValue?.toSignificant(6, { groupSeparator: ',' })}
|
||||
text={fiatValue?.toFixed(visibleDecimalPlaces, { groupSeparator: ',' })}
|
||||
textColor={fiatValue ? theme.text3 : theme.text4}
|
||||
/>
|
||||
</Trans>
|
||||
|
||||
@@ -5,17 +5,23 @@ import styled from 'styled-components/macro'
|
||||
|
||||
import Logo from '../Logo'
|
||||
|
||||
const StyledLogo = styled(Logo)<{ size: string; native: boolean }>`
|
||||
const StyledLogo = styled(Logo)<{ size: string }>`
|
||||
width: ${({ size }) => size};
|
||||
height: ${({ size }) => size};
|
||||
background: radial-gradient(white 50%, #ffffff00 calc(75% + 1px), #ffffff00 100%);
|
||||
border-radius: 50%;
|
||||
-mox-box-shadow: 0 0 1px ${({ native }) => (native ? 'white' : 'black')};
|
||||
-webkit-box-shadow: 0 0 1px ${({ native }) => (native ? 'white' : 'black')};
|
||||
box-shadow: 0 0 1px ${({ native }) => (native ? 'white' : 'black')};
|
||||
-mox-box-shadow: 0 0 1px black;
|
||||
-webkit-box-shadow: 0 0 1px black;
|
||||
box-shadow: 0 0 1px black;
|
||||
border: 0px solid rgba(255, 255, 255, 0);
|
||||
`
|
||||
|
||||
const StyledNativeLogo = styled(StyledLogo)`
|
||||
-mox-box-shadow: 0 0 1px white;
|
||||
-webkit-box-shadow: 0 0 1px white;
|
||||
box-shadow: 0 0 1px white;
|
||||
`
|
||||
|
||||
export default function CurrencyLogo({
|
||||
currency,
|
||||
size = '24px',
|
||||
@@ -26,16 +32,13 @@ export default function CurrencyLogo({
|
||||
size?: string
|
||||
style?: React.CSSProperties
|
||||
}) {
|
||||
const logoURIs = useCurrencyLogoURIs(currency)
|
||||
const props = {
|
||||
alt: `${currency?.symbol ?? 'token'} logo`,
|
||||
size,
|
||||
srcs: useCurrencyLogoURIs(currency),
|
||||
style,
|
||||
...rest,
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledLogo
|
||||
size={size}
|
||||
native={currency?.isNative ?? false}
|
||||
srcs={logoURIs}
|
||||
alt={`${currency?.symbol ?? 'token'} logo`}
|
||||
style={style}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
return currency?.isNative ? <StyledNativeLogo {...props} /> : <StyledLogo {...props} />
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import React, { ErrorInfo } from 'react'
|
||||
import ReactGA from 'react-ga'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import store, { AppState } from '../../state'
|
||||
@@ -49,6 +49,13 @@ type ErrorBoundaryState = {
|
||||
|
||||
const IS_UNISWAP = window.location.hostname === 'app.uniswap.org'
|
||||
|
||||
async function updateServiceWorker(): Promise<ServiceWorkerRegistration> {
|
||||
const ready = await navigator.serviceWorker.ready
|
||||
// the return type of update is incorrectly typed as Promise<void>. See
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/update
|
||||
return ready.update() as unknown as Promise<ServiceWorkerRegistration>
|
||||
}
|
||||
|
||||
export default class ErrorBoundary extends React.Component<unknown, ErrorBoundaryState> {
|
||||
constructor(props: unknown) {
|
||||
super(props)
|
||||
@@ -56,13 +63,30 @@ export default class ErrorBoundary extends React.Component<unknown, ErrorBoundar
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||||
updateServiceWorker()
|
||||
.then(async (registration) => {
|
||||
// We want to refresh only if we detect a new service worker is waiting to be activated.
|
||||
// See details about it: https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle
|
||||
if (registration?.waiting) {
|
||||
await registration.unregister()
|
||||
|
||||
// Makes Workbox call skipWaiting(). For more info on skipWaiting see: https://developer.chrome.com/docs/workbox/handling-service-worker-updates/
|
||||
registration.waiting.postMessage({ type: 'SKIP_WAITING' })
|
||||
|
||||
// Once the service worker is unregistered, we can reload the page to let
|
||||
// the browser download a fresh copy of our app (invalidating the cache)
|
||||
window.location.reload()
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to update service worker', error)
|
||||
})
|
||||
return { error }
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
ReactGA.exception({
|
||||
...error,
|
||||
...errorInfo,
|
||||
sendEvent('exception', {
|
||||
description: error.toString() + errorInfo.toString(),
|
||||
fatal: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { ButtonGray } from 'components/Button'
|
||||
import Card from 'components/Card'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
@@ -10,8 +11,7 @@ import { useFeeTierDistribution } from 'hooks/useFeeTierDistribution'
|
||||
import { PoolState, usePools } from 'hooks/usePools'
|
||||
import usePrevious from 'hooks/usePrevious'
|
||||
import { DynamicSection } from 'pages/AddLiquidity/styled'
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import ReactGA from 'react-ga'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Box } from 'rebass'
|
||||
import styled, { keyframes } from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
@@ -101,7 +101,7 @@ export default function FeeSelector({
|
||||
|
||||
const handleFeePoolSelectWithEvent = useCallback(
|
||||
(fee: FeeAmount) => {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'FeePoolSelect',
|
||||
action: 'Manual',
|
||||
})
|
||||
@@ -122,7 +122,7 @@ export default function FeeSelector({
|
||||
setShowOptions(false)
|
||||
|
||||
recommended.current = true
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'FeePoolSelect',
|
||||
action: ' Recommended',
|
||||
})
|
||||
|
||||
@@ -10,7 +10,7 @@ export const FEE_AMOUNT_DETAIL: Record<
|
||||
[FeeAmount.LOWEST]: {
|
||||
label: '0.01',
|
||||
description: <Trans>Best for very stable pairs.</Trans>,
|
||||
supportedChains: [SupportedChainId.MAINNET],
|
||||
supportedChains: [SupportedChainId.MAINNET, SupportedChainId.POLYGON, SupportedChainId.POLYGON_MUMBAI],
|
||||
},
|
||||
[FeeAmount.LOW]: {
|
||||
label: '0.05',
|
||||
|
||||
@@ -93,6 +93,13 @@ const FlyoutRowActiveIndicator = styled.div`
|
||||
height: 9px;
|
||||
width: 9px;
|
||||
`
|
||||
|
||||
const CircleContainer = styled.div`
|
||||
width: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
const LinkOutCircle = styled(ArrowDownCircle)`
|
||||
transform: rotate(230deg);
|
||||
width: 16px;
|
||||
@@ -146,7 +153,7 @@ const BridgeLabel = ({ chainId }: { chainId: SupportedChainId }) => {
|
||||
return <Trans>Arbitrum Bridge</Trans>
|
||||
case SupportedChainId.OPTIMISM:
|
||||
case SupportedChainId.OPTIMISTIC_KOVAN:
|
||||
return <Trans>Optimism Gateway</Trans>
|
||||
return <Trans>Optimism Bridge</Trans>
|
||||
case SupportedChainId.POLYGON:
|
||||
case SupportedChainId.POLYGON_MUMBAI:
|
||||
return <Trans>Polygon Bridge</Trans>
|
||||
@@ -188,7 +195,11 @@ function Row({
|
||||
<FlyoutRow onClick={() => onSelectChain(targetChain)} active={active}>
|
||||
<Logo src={logoUrl} />
|
||||
<NetworkLabel>{label}</NetworkLabel>
|
||||
{chainId === targetChain && <FlyoutRowActiveIndicator />}
|
||||
{chainId === targetChain && (
|
||||
<CircleContainer>
|
||||
<FlyoutRowActiveIndicator />
|
||||
</CircleContainer>
|
||||
)}
|
||||
</FlyoutRow>
|
||||
)
|
||||
|
||||
@@ -197,21 +208,30 @@ function Row({
|
||||
<ActiveRowWrapper>
|
||||
{rowContent}
|
||||
<ActiveRowLinkList>
|
||||
{bridge ? (
|
||||
{bridge && (
|
||||
<ExternalLink href={bridge}>
|
||||
<BridgeLabel chainId={chainId} /> <LinkOutCircle />
|
||||
<BridgeLabel chainId={chainId} />
|
||||
<CircleContainer>
|
||||
<LinkOutCircle />
|
||||
</CircleContainer>
|
||||
</ExternalLink>
|
||||
) : null}
|
||||
{explorer ? (
|
||||
)}
|
||||
{explorer && (
|
||||
<ExternalLink href={explorer}>
|
||||
<ExplorerLabel chainId={chainId} /> <LinkOutCircle />
|
||||
<ExplorerLabel chainId={chainId} />
|
||||
<CircleContainer>
|
||||
<LinkOutCircle />
|
||||
</CircleContainer>
|
||||
</ExternalLink>
|
||||
) : null}
|
||||
{helpCenterUrl ? (
|
||||
)}
|
||||
{helpCenterUrl && (
|
||||
<ExternalLink href={helpCenterUrl}>
|
||||
<Trans>Help Center</Trans> <LinkOutCircle />
|
||||
<Trans>Help Center</Trans>
|
||||
<CircleContainer>
|
||||
<LinkOutCircle />
|
||||
</CircleContainer>
|
||||
</ExternalLink>
|
||||
) : null}
|
||||
)}
|
||||
</ActiveRowLinkList>
|
||||
</ActiveRowWrapper>
|
||||
)
|
||||
@@ -255,8 +275,8 @@ export default function NetworkSelector() {
|
||||
|
||||
const handleChainSwitch = useCallback(
|
||||
(targetChain: number, skipToggle?: boolean) => {
|
||||
if (!library) return
|
||||
switchToNetwork({ library, chainId: targetChain })
|
||||
if (!library?.provider) return
|
||||
switchToNetwork({ provider: library.provider, chainId: targetChain })
|
||||
.then(() => {
|
||||
if (!skipToggle) {
|
||||
toggle()
|
||||
|
||||
@@ -3,9 +3,8 @@ import { AbstractConnector } from 'web3-react-abstract-connector'
|
||||
|
||||
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
|
||||
import FortmaticIcon from '../../assets/images/fortmaticIcon.png'
|
||||
import PortisIcon from '../../assets/images/portisIcon.png'
|
||||
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
|
||||
import { fortmatic, injected, portis, walletconnect, walletlink } from '../../connectors'
|
||||
import { fortmatic, injected, walletconnect, walletlink } from '../../connectors'
|
||||
import Identicon from '../Identicon'
|
||||
|
||||
export default function StatusIcon({ connector }: { connector: AbstractConnector | Connector }) {
|
||||
@@ -18,8 +17,6 @@ export default function StatusIcon({ connector }: { connector: AbstractConnector
|
||||
return <img src={CoinbaseWalletIcon} alt={'Coinbase Wallet'} />
|
||||
case fortmatic:
|
||||
return <img src={FortmaticIcon} alt={'Fortmatic'} />
|
||||
case portis:
|
||||
return <img src={PortisIcon} alt={'Portis'} />
|
||||
default:
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, Price, Token } from '@uniswap/sdk-core'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { AutoColumn, ColumnCenter } from 'components/Column'
|
||||
import Loader from 'components/Loader'
|
||||
import { format } from 'd3'
|
||||
@@ -9,7 +10,6 @@ import useTheme from 'hooks/useTheme'
|
||||
import { saturate } from 'polished'
|
||||
import React, { ReactNode, useCallback, useMemo } from 'react'
|
||||
import { BarChart2, CloudOff, Inbox } from 'react-feather'
|
||||
import ReactGA from 'react-ga'
|
||||
import { batch } from 'react-redux'
|
||||
import { Bound } from 'state/mint/v3/actions'
|
||||
import styled from 'styled-components/macro'
|
||||
@@ -158,11 +158,7 @@ export default function LiquidityChartRangeInput({
|
||||
)
|
||||
|
||||
if (isError) {
|
||||
ReactGA.exception({
|
||||
...error,
|
||||
category: 'Liquidity',
|
||||
fatal: false,
|
||||
})
|
||||
sendEvent('exception', { description: error.toString(), fatal: false })
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { Heart, X } from 'react-feather'
|
||||
import ReactGA from 'react-ga'
|
||||
import styled, { keyframes } from 'styled-components/macro'
|
||||
|
||||
import tokenLogo from '../../assets/images/token-logo.png'
|
||||
@@ -65,7 +65,7 @@ export default function ClaimPopup() {
|
||||
const showClaimModal = useModalOpen(ApplicationModal.SELF_CLAIM)
|
||||
const toggleSelfClaimModal = useToggleSelfClaimModal()
|
||||
const handleToggleSelfClaimModal = useCallback(() => {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'MerkleDrop',
|
||||
action: 'Toggle self claim modal',
|
||||
})
|
||||
@@ -79,7 +79,7 @@ export default function ClaimPopup() {
|
||||
// listen for available claim and show popup if needed
|
||||
useEffect(() => {
|
||||
if (userHasAvailableclaim) {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'MerkleDrop',
|
||||
action: 'Show claim popup',
|
||||
})
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import FlagImage from 'assets/images/ukraine.png'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { RowBetween, RowFixed } from 'components/Row'
|
||||
import { X } from 'react-feather'
|
||||
import ReactGA from 'react-ga'
|
||||
import { useDarkModeManager, useShowDonationLink } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, ThemedText, Z_INDEX } from 'theme'
|
||||
|
||||
const darkGradient = `radial-gradient(87.53% 3032.45% at 5.16% 10.13%, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 100%),
|
||||
linear-gradient(0deg, rgba(0, 91, 187, 0.35), rgba(0, 91, 187, 0.35)), #000000;`
|
||||
const lightGradient = `radial-gradient(87.53% 3032.45% at 5.16% 10.13%, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 100%), linear-gradient(0deg, #CBE4FF, #CBE4FF), linear-gradient(0deg, rgba(255, 255, 255, 0.09), rgba(255, 255, 255, 0.09)), radial-gradient(100% 93.36% at 0% 6.64%, #8BC4FF 0%, #FFF5BF 100%);`
|
||||
|
||||
const Wrapper = styled(AutoColumn)<{ darkMode: boolean }>`
|
||||
background: #edeef2;
|
||||
position: fixed;
|
||||
bottom: 40px;
|
||||
border-radius: 12px;
|
||||
padding: 18px;
|
||||
max-width: 360px;
|
||||
background: ${({ darkMode }) => (darkMode ? darkGradient : lightGradient)};
|
||||
color: ${({ theme }) => theme.text1};
|
||||
z-index: ${Z_INDEX.deprecated_content};
|
||||
|
||||
:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
& > * {
|
||||
z-index: ${Z_INDEX.fixed};
|
||||
}
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
:before {
|
||||
background-image: url(${FlagImage});
|
||||
background-repeat: no-repeat;
|
||||
overflow: hidden;
|
||||
background-size: 300px;
|
||||
content: '';
|
||||
height: 1200px;
|
||||
width: 400px;
|
||||
opacity: 0.1;
|
||||
position: absolute;
|
||||
transform: rotate(25deg) translate(-140px, -60px);
|
||||
width: 300px;
|
||||
z-index: ${Z_INDEX.deprecated_zero};
|
||||
}
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
max-width: 100%;
|
||||
`}
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
position: relative;
|
||||
bottom: unset;
|
||||
`}
|
||||
`
|
||||
|
||||
const WrappedCloseIcon = styled(X)`
|
||||
stroke: ${({ theme }) => theme.text2};
|
||||
z-index: ${Z_INDEX.tooltip};
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
}
|
||||
`
|
||||
|
||||
export const StyledFlagImage = styled.div`
|
||||
margin-right: 12px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 100%;
|
||||
&:before,
|
||||
&:after {
|
||||
content: '';
|
||||
width: 9px;
|
||||
height: 18px;
|
||||
}
|
||||
&:before {
|
||||
float: left;
|
||||
border-top-left-radius: 9px;
|
||||
border-bottom-left-radius: 9px;
|
||||
background: #005bbb;
|
||||
}
|
||||
&:after {
|
||||
float: right;
|
||||
border-top-right-radius: 9px;
|
||||
border-bottom-right-radius: 9px;
|
||||
background: #ffd500;
|
||||
}
|
||||
transform: rotate(90deg);
|
||||
`
|
||||
|
||||
const StyledLink = styled(ExternalLink)`
|
||||
text-decoration: none !important;
|
||||
`
|
||||
|
||||
export default function DonationLink() {
|
||||
const [darkMode] = useDarkModeManager()
|
||||
const [, setVisible] = useShowDonationLink()
|
||||
|
||||
return (
|
||||
<Wrapper
|
||||
gap="10px"
|
||||
darkMode={darkMode}
|
||||
as={StyledLink}
|
||||
target="https://donate.uniswap.org/#/swap"
|
||||
href="https://donate.uniswap.org/#/swap"
|
||||
onClickCapture={() => {
|
||||
ReactGA.event({
|
||||
category: 'Donate',
|
||||
action: 'Link to Ukraine site.',
|
||||
})
|
||||
}}
|
||||
>
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<StyledFlagImage />
|
||||
<ThemedText.Body fontWeight={600} fontSize={'18px'}>
|
||||
<Trans>Donate to Ukraine</Trans>
|
||||
</ThemedText.Body>
|
||||
</RowFixed>
|
||||
<WrappedCloseIcon
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setVisible(false)
|
||||
return false
|
||||
}}
|
||||
/>
|
||||
</RowBetween>
|
||||
<ThemedText.Body fontWeight={400} fontSize="12px" color="text2">
|
||||
<Trans>Directly support the Ukrainian government by donating tokens.</Trans>
|
||||
</ThemedText.Body>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { RowFixed } from 'components/Row'
|
||||
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
|
||||
import { useEffect } from 'react'
|
||||
import { MessageCircle, X } from 'react-feather'
|
||||
import ReactGA from 'react-ga'
|
||||
import { useShowSurveyPopup } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, ThemedText, Z_INDEX } from 'theme'
|
||||
@@ -62,7 +62,7 @@ export default function SurveyPopup() {
|
||||
if (Math.random() < 0.01) {
|
||||
setShowSurveyPopup(true)
|
||||
// log a case of succesful view
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Survey',
|
||||
action: 'Saw Survey',
|
||||
})
|
||||
@@ -80,7 +80,7 @@ export default function SurveyPopup() {
|
||||
<Wrapper gap="10px">
|
||||
<WrappedCloseIcon
|
||||
onClick={() => {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Survey',
|
||||
action: 'Clicked Survey Link',
|
||||
})
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
import { MEDIA_WIDTHS } from 'theme'
|
||||
|
||||
import { useActivePopups } from '../../state/application/hooks'
|
||||
import { useShowDonationLink, useURLWarningVisible } from '../../state/user/hooks'
|
||||
import { useURLWarningVisible } from '../../state/user/hooks'
|
||||
import { AutoColumn } from '../Column'
|
||||
import ClaimPopup from './ClaimPopup'
|
||||
import DonationLink from './DonationLink'
|
||||
import PopupItem from './PopupItem'
|
||||
|
||||
const MobilePopupWrapper = styled.div<{ height: string | number }>`
|
||||
@@ -68,11 +66,6 @@ export default function Popups() {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const isNotOnMainnet = Boolean(chainId && chainId !== SupportedChainId.MAINNET)
|
||||
|
||||
const location = useLocation()
|
||||
const isOnSwapPage = location.pathname.includes('swap')
|
||||
const [donationVisible] = useShowDonationLink()
|
||||
const showDonation = donationVisible && isOnSwapPage
|
||||
|
||||
return (
|
||||
<>
|
||||
<FixedPopupColumn gap="20px" extraPadding={urlWarningActive} xlPadding={isNotOnMainnet}>
|
||||
@@ -80,11 +73,9 @@ export default function Popups() {
|
||||
{activePopups.map((item) => (
|
||||
<PopupItem key={item.key} content={item.content} popKey={item.key} removeAfterMs={item.removeAfterMs} />
|
||||
))}
|
||||
{showDonation ? <DonationLink /> : null}
|
||||
</FixedPopupColumn>
|
||||
<MobilePopupWrapper height={activePopups?.length > 0 || showDonation ? 'fit-content' : 0}>
|
||||
<MobilePopupWrapper height={activePopups?.length > 0 ? 'fit-content' : 0}>
|
||||
<MobilePopupInner>
|
||||
{showDonation ? <DonationLink /> : null}
|
||||
{activePopups // reverse so new items up front
|
||||
.slice(0)
|
||||
.reverse()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { ButtonText } from 'components/Button'
|
||||
import PositionListItem from 'components/PositionListItem'
|
||||
import Toggle from 'components/Toggle'
|
||||
import React from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { MEDIA_WIDTHS } from 'theme'
|
||||
@@ -28,9 +28,37 @@ const MobileHeader = styled.div`
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: ${MEDIA_WIDTHS.upToExtraSmall}px) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
}
|
||||
`
|
||||
|
||||
const ToggleWrap = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const ToggleLabel = styled.div`
|
||||
opacity: 0.6;
|
||||
margin-right: 10px;
|
||||
`
|
||||
|
||||
const MobileTogglePosition = styled.div`
|
||||
@media screen and (max-width: ${MEDIA_WIDTHS.upToExtraSmall}px) {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
}
|
||||
`
|
||||
|
||||
type PositionListProps = React.PropsWithChildren<{
|
||||
@@ -51,12 +79,35 @@ export default function PositionList({
|
||||
<Trans>Your positions</Trans>
|
||||
{positions && ' (' + positions.length + ')'}
|
||||
</div>
|
||||
<ButtonText style={{ opacity: 0.6 }} onClick={() => setUserHideClosedPositions(!userHideClosedPositions)}>
|
||||
<Trans>Hide closed positions</Trans>
|
||||
</ButtonText>
|
||||
<ToggleWrap>
|
||||
<ToggleLabel>
|
||||
<Trans>Show closed positions</Trans>
|
||||
</ToggleLabel>
|
||||
<Toggle
|
||||
id="desktop-hide-closed-positions"
|
||||
isActive={!userHideClosedPositions}
|
||||
toggle={() => {
|
||||
setUserHideClosedPositions(!userHideClosedPositions)
|
||||
}}
|
||||
/>
|
||||
</ToggleWrap>
|
||||
</DesktopHeader>
|
||||
<MobileHeader>
|
||||
<Trans>Your positions</Trans>
|
||||
<ToggleWrap>
|
||||
<ToggleLabel>
|
||||
<Trans>Show closed positions</Trans>
|
||||
</ToggleLabel>
|
||||
<MobileTogglePosition>
|
||||
<Toggle
|
||||
id="mobile-hide-closed-positions"
|
||||
isActive={!userHideClosedPositions}
|
||||
toggle={() => {
|
||||
setUserHideClosedPositions(!userHideClosedPositions)
|
||||
}}
|
||||
/>
|
||||
</MobileTogglePosition>
|
||||
</ToggleWrap>
|
||||
</MobileHeader>
|
||||
{positions.map((p) => {
|
||||
return <PositionListItem key={p.tokenId.toString()} positionDetails={p} />
|
||||
|
||||
@@ -157,7 +157,7 @@ export function getPriceOrderingFromPositionForUI(position?: Position): {
|
||||
|
||||
// if token1 is an ETH-/BTC-stable asset, set it as the base token
|
||||
const bases = [...Object.values(WRAPPED_NATIVE_CURRENCY), WBTC]
|
||||
if (bases.some((base) => base.equals(token1))) {
|
||||
if (bases.some((base) => base && base.equals(token1))) {
|
||||
return {
|
||||
priceLower: position.token0PriceUpper.invert(),
|
||||
priceUpper: position.token0PriceLower.invert(),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import Card, { DarkGreyCard } from 'components/Card'
|
||||
import Row, { AutoRow, RowBetween } from 'components/Row'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { ArrowDown, Info, X } from 'react-feather'
|
||||
import ReactGA from 'react-ga'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, ThemedText } from 'theme'
|
||||
import { isMobile } from 'utils/userAgent'
|
||||
@@ -87,7 +87,7 @@ export function PrivacyPolicyModal() {
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Modal',
|
||||
action: 'Show Legal',
|
||||
})
|
||||
@@ -167,11 +167,13 @@ export function PrivacyPolicy() {
|
||||
</AutoColumn>
|
||||
</DarkGreyCard>
|
||||
))}
|
||||
<Row justify="center" marginBottom="1rem">
|
||||
<ExternalLink href="https://help.uniswap.org/en/articles/5675203-terms-of-service-faq">
|
||||
<Trans>Learn more</Trans>
|
||||
</ExternalLink>
|
||||
</Row>
|
||||
<ThemedText.Body fontSize={12}>
|
||||
<Row justify="center" marginBottom="1rem">
|
||||
<ExternalLink href="https://help.uniswap.org/en/articles/5675203-terms-of-service-faq">
|
||||
<Trans>Learn more</Trans>
|
||||
</ExternalLink>
|
||||
</Row>
|
||||
</ThemedText.Body>
|
||||
</AutoColumn>
|
||||
</AutoColumn>
|
||||
</Wrapper>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { ButtonOutlined } from 'components/Button'
|
||||
import { AutoRow } from 'components/Row'
|
||||
import React from 'react'
|
||||
import ReactGA from 'react-ga'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
|
||||
@@ -20,7 +20,7 @@ export default function PresetsButtons({ setFullRange }: { setFullRange: () => v
|
||||
<Button
|
||||
onClick={() => {
|
||||
setFullRange()
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Liquidity',
|
||||
action: 'Full Range Clicked',
|
||||
})
|
||||
|
||||
@@ -5,8 +5,8 @@ import Badge from 'components/Badge'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import DoubleCurrencyLogo from 'components/DoubleLogo'
|
||||
import Row, { AutoRow } from 'components/Row'
|
||||
import { RoutingDiagramEntry } from 'components/swap/SwapRoute'
|
||||
import { useTokenInfoFromActiveList } from 'hooks/useTokenInfoFromActiveList'
|
||||
import { RoutingDiagramEntry } from 'lib/components/Swap/RoutingDiagram/utils'
|
||||
import { Box } from 'rebass'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText, Z_INDEX } from 'theme'
|
||||
|
||||
@@ -49,6 +49,8 @@ export default function CommonBases({
|
||||
const isSelected = selectedCurrency?.equals(currency)
|
||||
return (
|
||||
<BaseWrapper
|
||||
tabIndex={0}
|
||||
onKeyPress={(e) => !isSelected && e.key === 'Enter' && onSelect(currency)}
|
||||
onClick={() => !isSelected && onSelect(currency)}
|
||||
disable={isSelected}
|
||||
key={currencyId(currency)}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders currency rows correctly when currencies list is non-empty 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
style="position: relative; height: 10px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
||||
>
|
||||
<div
|
||||
style="height: 168px; width: 100%;"
|
||||
>
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 Row__RowBetween-sc-u7azg8-1 styleds__MenuItem-sc-muzgnq-3 lmTMKd hLLNig hzJkYd firMKT token-item-0x6B175474E89094C44Da98b954EedeAC495271d0F"
|
||||
style="position: absolute; left: 0px; top: 0px; height: 56px; width: 100%;"
|
||||
tabindex="0"
|
||||
>
|
||||
CurrencyLogo currency=DAI
|
||||
<div
|
||||
class="Column-sc-1r2yyln-0 cYEAJI"
|
||||
>
|
||||
<div
|
||||
class="css-8mokm4"
|
||||
title="Dai Stablecoin"
|
||||
>
|
||||
DAI
|
||||
</div>
|
||||
<div
|
||||
class="theme__TextWrapper-sc-5lu8um-0 gVIOIC css-165qfk5"
|
||||
>
|
||||
Dai Stablecoin
|
||||
</div>
|
||||
</div>
|
||||
<span />
|
||||
</div>
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 Row__RowBetween-sc-u7azg8-1 styleds__MenuItem-sc-muzgnq-3 lmTMKd hLLNig hzJkYd firMKT token-item-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
style="position: absolute; left: 0px; top: 56px; height: 56px; width: 100%;"
|
||||
tabindex="0"
|
||||
>
|
||||
CurrencyLogo currency=USDC
|
||||
<div
|
||||
class="Column-sc-1r2yyln-0 cYEAJI"
|
||||
>
|
||||
<div
|
||||
class="css-8mokm4"
|
||||
title="USD//C"
|
||||
>
|
||||
USDC
|
||||
</div>
|
||||
<div
|
||||
class="theme__TextWrapper-sc-5lu8um-0 gVIOIC css-165qfk5"
|
||||
>
|
||||
USD//C
|
||||
</div>
|
||||
</div>
|
||||
<span />
|
||||
</div>
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 Row__RowBetween-sc-u7azg8-1 styleds__MenuItem-sc-muzgnq-3 lmTMKd hLLNig hzJkYd firMKT token-item-0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
|
||||
style="position: absolute; left: 0px; top: 112px; height: 56px; width: 100%;"
|
||||
tabindex="0"
|
||||
>
|
||||
CurrencyLogo currency=WBTC
|
||||
<div
|
||||
class="Column-sc-1r2yyln-0 cYEAJI"
|
||||
>
|
||||
<div
|
||||
class="css-8mokm4"
|
||||
title="Wrapped BTC"
|
||||
>
|
||||
WBTC
|
||||
</div>
|
||||
<div
|
||||
class="theme__TextWrapper-sc-5lu8um-0 gVIOIC css-165qfk5"
|
||||
>
|
||||
Wrapped BTC
|
||||
</div>
|
||||
</div>
|
||||
<span />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`renders loading rows when isLoading is true 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
style="position: relative; height: 10px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
||||
>
|
||||
<div
|
||||
style="height: 0px; width: 100%;"
|
||||
/>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
73
src/components/SearchModal/CurrencyList/index.test.tsx
Normal file
73
src/components/SearchModal/CurrencyList/index.test.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import { Currency, CurrencyAmount as mockCurrencyAmount, Token as mockToken } from '@uniswap/sdk-core'
|
||||
import { DAI, USDC_MAINNET, WBTC } from 'constants/tokens'
|
||||
import * as mockJSBI from 'jsbi'
|
||||
import { render } from 'test-utils'
|
||||
|
||||
import CurrencyList from '.'
|
||||
|
||||
const noOp = function () {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
const mockCurrencyAmt = {
|
||||
[DAI.address]: mockCurrencyAmount.fromRawAmount(DAI, mockJSBI.default.BigInt(100)),
|
||||
[USDC_MAINNET.address]: mockCurrencyAmount.fromRawAmount(USDC_MAINNET, mockJSBI.default.BigInt(10)),
|
||||
[WBTC.address]: mockCurrencyAmount.fromRawAmount(WBTC, mockJSBI.default.BigInt(1)),
|
||||
}
|
||||
|
||||
jest.mock(
|
||||
'components/CurrencyLogo',
|
||||
() =>
|
||||
({ currency }: { currency: Currency }) =>
|
||||
`CurrencyLogo currency=${currency.symbol}`
|
||||
)
|
||||
|
||||
jest.mock('hooks/useActiveWeb3React', () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
default: () => ({
|
||||
account: '123',
|
||||
active: true,
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
jest.mock('../../../state/wallet/hooks', () => {
|
||||
return {
|
||||
useCurrencyBalance: (currency: Currency) => {
|
||||
return mockCurrencyAmt[(currency as mockToken).address]
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
it('renders loading rows when isLoading is true', () => {
|
||||
const { asFragment } = render(
|
||||
<CurrencyList
|
||||
height={10}
|
||||
currencies={[]}
|
||||
otherListTokens={[]}
|
||||
selectedCurrency={null}
|
||||
onCurrencySelect={noOp}
|
||||
showImportView={noOp}
|
||||
setImportToken={noOp}
|
||||
isLoading={true}
|
||||
/>
|
||||
)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('renders currency rows correctly when currencies list is non-empty', () => {
|
||||
const { asFragment } = render(
|
||||
<CurrencyList
|
||||
height={10}
|
||||
currencies={[DAI, USDC_MAINNET, WBTC]}
|
||||
otherListTokens={[]}
|
||||
selectedCurrency={null}
|
||||
onCurrencySelect={noOp}
|
||||
showImportView={noOp}
|
||||
setImportToken={noOp}
|
||||
isLoading={false}
|
||||
/>
|
||||
)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
})
|
||||
@@ -9,20 +9,20 @@ import { FixedSizeList } from 'react-window'
|
||||
import { Text } from 'rebass'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import TokenListLogo from '../../assets/svg/tokenlist.svg'
|
||||
import { useIsUserAddedToken } from '../../hooks/Tokens'
|
||||
import { useCombinedActiveList } from '../../state/lists/hooks'
|
||||
import { WrappedTokenInfo } from '../../state/lists/wrappedTokenInfo'
|
||||
import { useCurrencyBalance } from '../../state/wallet/hooks'
|
||||
import { ThemedText } from '../../theme'
|
||||
import { isTokenOnList } from '../../utils'
|
||||
import Column from '../Column'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import Loader from '../Loader'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
import { MouseoverTooltip } from '../Tooltip'
|
||||
import ImportRow from './ImportRow'
|
||||
import { MenuItem } from './styleds'
|
||||
import TokenListLogo from '../../../assets/svg/tokenlist.svg'
|
||||
import { useIsUserAddedToken } from '../../../hooks/Tokens'
|
||||
import { useCombinedActiveList } from '../../../state/lists/hooks'
|
||||
import { WrappedTokenInfo } from '../../../state/lists/wrappedTokenInfo'
|
||||
import { useCurrencyBalance } from '../../../state/wallet/hooks'
|
||||
import { ThemedText } from '../../../theme'
|
||||
import { isTokenOnList } from '../../../utils'
|
||||
import Column from '../../Column'
|
||||
import CurrencyLogo from '../../CurrencyLogo'
|
||||
import Loader from '../../Loader'
|
||||
import { RowBetween, RowFixed } from '../../Row'
|
||||
import { MouseoverTooltip } from '../../Tooltip'
|
||||
import ImportRow from '../ImportRow'
|
||||
import { LoadingRows, MenuItem } from '../styleds'
|
||||
|
||||
function currencyKey(currency: Currency): string {
|
||||
return currency.isToken ? currency.address : 'ETHER'
|
||||
@@ -124,8 +124,10 @@ function CurrencyRow({
|
||||
// only show add or remove buttons if not on selected list
|
||||
return (
|
||||
<MenuItem
|
||||
tabIndex={0}
|
||||
style={style}
|
||||
className={`token-item-${key}`}
|
||||
onKeyPress={(e) => (!isSelected && e.key === 'Enter' ? onSelect() : null)}
|
||||
onClick={() => (isSelected ? null : onSelect())}
|
||||
disabled={isSelected}
|
||||
selected={otherSelected}
|
||||
@@ -195,6 +197,7 @@ export default function CurrencyList({
|
||||
showImportView,
|
||||
setImportToken,
|
||||
showCurrencyAmount,
|
||||
isLoading,
|
||||
}: {
|
||||
height: number
|
||||
currencies: Currency[]
|
||||
@@ -206,6 +209,7 @@ export default function CurrencyList({
|
||||
showImportView: () => void
|
||||
setImportToken: (token: Token) => void
|
||||
showCurrencyAmount?: boolean
|
||||
isLoading: boolean
|
||||
}) {
|
||||
const itemData: (Currency | BreakLine)[] = useMemo(() => {
|
||||
if (otherListTokens && otherListTokens?.length > 0) {
|
||||
@@ -232,7 +236,15 @@ export default function CurrencyList({
|
||||
|
||||
const showImport = index > currencies.length
|
||||
|
||||
if (showImport && token) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<LoadingRows>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</LoadingRows>
|
||||
)
|
||||
} else if (showImport && token) {
|
||||
return (
|
||||
<ImportRow style={style} token={token} showImportView={showImportView} setImportToken={setImportToken} dim />
|
||||
)
|
||||
@@ -259,6 +271,7 @@ export default function CurrencyList({
|
||||
setImportToken,
|
||||
showImportView,
|
||||
showCurrencyAmount,
|
||||
isLoading,
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { Currency, Token } from '@uniswap/sdk-core'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import useDebounce from 'hooks/useDebounce'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
@@ -11,7 +12,6 @@ import { getTokenFilter } from 'lib/hooks/useTokenList/filtering'
|
||||
import { tokenComparator, useSortTokensByQuery } from 'lib/hooks/useTokenList/sorting'
|
||||
import { KeyboardEvent, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Edit } from 'react-feather'
|
||||
import ReactGA from 'react-ga'
|
||||
import AutoSizer from 'react-virtualized-auto-sizer'
|
||||
import { FixedSizeList } from 'react-window'
|
||||
import { Text } from 'rebass'
|
||||
@@ -74,6 +74,8 @@ export function CurrencySearch({
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const theme = useTheme()
|
||||
|
||||
const [tokenLoaderTimerElapsed, setTokenLoaderTimerElapsed] = useState(false)
|
||||
|
||||
// refs for fixed size lists
|
||||
const fixedList = useRef<FixedSizeList>()
|
||||
|
||||
@@ -91,7 +93,7 @@ export function CurrencySearch({
|
||||
|
||||
useEffect(() => {
|
||||
if (isAddressSearch) {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Currency Select',
|
||||
action: 'Search by address',
|
||||
label: isAddressSearch,
|
||||
@@ -103,10 +105,11 @@ export function CurrencySearch({
|
||||
return Object.values(allTokens).filter(getTokenFilter(debouncedQuery))
|
||||
}, [allTokens, debouncedQuery])
|
||||
|
||||
const balances = useAllTokenBalances()
|
||||
const [balances, balancesIsLoading] = useAllTokenBalances()
|
||||
const sortedTokens: Token[] = useMemo(() => {
|
||||
return filteredTokens.sort(tokenComparator.bind(null, balances))
|
||||
}, [balances, filteredTokens])
|
||||
void balancesIsLoading // creates a new array once balances load to update hooks
|
||||
return [...filteredTokens].sort(tokenComparator.bind(null, balances))
|
||||
}, [balances, filteredTokens, balancesIsLoading])
|
||||
|
||||
const filteredSortedTokens = useSortTokensByQuery(debouncedQuery, sortedTokens)
|
||||
|
||||
@@ -173,6 +176,14 @@ export function CurrencySearch({
|
||||
filteredTokens.length === 0 || (debouncedQuery.length > 2 && !isAddressSearch) ? debouncedQuery : undefined
|
||||
)
|
||||
|
||||
// Timeout token loader after 3 seconds to avoid hanging in a loading state.
|
||||
useEffect(() => {
|
||||
const tokenLoaderTimer = setTimeout(() => {
|
||||
setTokenLoaderTimerElapsed(true)
|
||||
}, 3000)
|
||||
return () => clearTimeout(tokenLoaderTimer)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<ContentWrapper>
|
||||
<PaddedColumn gap="16px">
|
||||
@@ -218,6 +229,7 @@ export function CurrencySearch({
|
||||
showImportView={showImportView}
|
||||
setImportToken={setImportToken}
|
||||
showCurrencyAmount={showCurrencyAmount}
|
||||
isLoading={balancesIsLoading && !tokenLoaderTimerElapsed}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { TokenList } from '@uniswap/token-lists'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { ButtonPrimary } from 'components/Button'
|
||||
import Card from 'components/Card'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
@@ -11,7 +12,6 @@ import useTheme from 'hooks/useTheme'
|
||||
import { transparentize } from 'polished'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { AlertTriangle, ArrowLeft } from 'react-feather'
|
||||
import ReactGA from 'react-ga'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
import { enableList, removeList } from 'state/lists/actions'
|
||||
import { useAllLists } from 'state/lists/hooks'
|
||||
@@ -54,7 +54,7 @@ export function ImportList({ listURL, list, setModalView, onDismiss }: ImportPro
|
||||
setAddError(null)
|
||||
fetchList(listURL)
|
||||
.then(() => {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Lists',
|
||||
action: 'Add List',
|
||||
label: listURL,
|
||||
@@ -66,7 +66,7 @@ export function ImportList({ listURL, list, setModalView, onDismiss }: ImportPro
|
||||
setModalView(CurrencyModalView.manage)
|
||||
})
|
||||
.catch((error) => {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Lists',
|
||||
action: 'Add List Failed',
|
||||
label: listURL,
|
||||
|
||||
@@ -63,7 +63,7 @@ export default function ImportRow({
|
||||
const list = token instanceof WrappedTokenInfo ? token.list : undefined
|
||||
|
||||
return (
|
||||
<TokenSection style={style}>
|
||||
<TokenSection tabIndex={0} style={style}>
|
||||
<CurrencyLogo currency={token} size={'24px'} style={{ opacity: dim ? '0.6' : '1' }} />
|
||||
<AutoColumn gap="4px" style={{ opacity: dim ? '0.6' : '1' }}>
|
||||
<AutoRow>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { TokenList } from '@uniswap/token-lists'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import Card from 'components/Card'
|
||||
import { UNSUPPORTED_LIST_URLS } from 'constants/lists'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
@@ -9,7 +10,6 @@ import parseENSAddress from 'lib/utils/parseENSAddress'
|
||||
import uriToHttp from 'lib/utils/uriToHttp'
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { CheckCircle, Settings } from 'react-feather'
|
||||
import ReactGA from 'react-ga'
|
||||
import { usePopper } from 'react-popper'
|
||||
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
@@ -26,7 +26,7 @@ import { ButtonEmpty, ButtonPrimary } from '../Button'
|
||||
import Column, { AutoColumn } from '../Column'
|
||||
import ListLogo from '../ListLogo'
|
||||
import Row, { RowBetween, RowFixed } from '../Row'
|
||||
import ListToggle from '../Toggle/ListToggle'
|
||||
import Toggle from '../Toggle'
|
||||
import { CurrencyModalView } from './CurrencySearchModal'
|
||||
import { PaddedColumn, SearchInput, Separator, SeparatorDark } from './styleds'
|
||||
|
||||
@@ -126,7 +126,7 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
|
||||
|
||||
const handleAcceptListUpdate = useCallback(() => {
|
||||
if (!pending) return
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Lists',
|
||||
action: 'Update List from List Select',
|
||||
label: listUrl,
|
||||
@@ -135,13 +135,13 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
|
||||
}, [dispatch, listUrl, pending])
|
||||
|
||||
const handleRemoveList = useCallback(() => {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Lists',
|
||||
action: 'Start Remove List',
|
||||
label: listUrl,
|
||||
})
|
||||
if (window.prompt(t`Please confirm you would like to remove this list by typing REMOVE`) === `REMOVE`) {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Lists',
|
||||
action: 'Confirm Remove List',
|
||||
label: listUrl,
|
||||
@@ -151,7 +151,7 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
|
||||
}, [dispatch, listUrl])
|
||||
|
||||
const handleEnableList = useCallback(() => {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Lists',
|
||||
action: 'Enable List',
|
||||
label: listUrl,
|
||||
@@ -160,7 +160,7 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
|
||||
}, [dispatch, listUrl])
|
||||
|
||||
const handleDisableList = useCallback(() => {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Lists',
|
||||
action: 'Disable List',
|
||||
label: listUrl,
|
||||
@@ -215,7 +215,7 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
|
||||
</StyledMenu>
|
||||
</RowFixed>
|
||||
</Column>
|
||||
<ListToggle
|
||||
<Toggle
|
||||
isActive={isActive}
|
||||
bgColor={listColor}
|
||||
toggle={() => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { LoadingRows as BaseLoadingRows } from 'components/Loader/styled'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { AutoColumn } from '../Column'
|
||||
@@ -72,3 +73,25 @@ export const SeparatorDark = styled.div`
|
||||
height: 1px;
|
||||
background-color: ${({ theme }) => theme.bg3};
|
||||
`
|
||||
|
||||
export const LoadingRows = styled(BaseLoadingRows)`
|
||||
grid-column-gap: 0.5em;
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
max-width: 960px;
|
||||
padding: 12px 20px;
|
||||
|
||||
& > div:nth-child(4n + 1) {
|
||||
grid-column: 1 / 8;
|
||||
height: 1em;
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
& > div:nth-child(4n + 2) {
|
||||
grid-column: 12;
|
||||
height: 1em;
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
& > div:nth-child(4n + 3) {
|
||||
grid-column: 1 / 4;
|
||||
height: 0.75em;
|
||||
}
|
||||
`
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { AUTO_ROUTER_SUPPORTED_CHAINS } from 'lib/hooks/routing/clientSideSmartOrderRouter'
|
||||
import { useContext, useRef, useState } from 'react'
|
||||
import { Settings, X } from 'react-feather'
|
||||
import ReactGA from 'react-ga'
|
||||
import { Text } from 'rebass'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
|
||||
@@ -211,7 +211,7 @@ export default function SettingsTab({ placeholderSlippage }: { placeholderSlippa
|
||||
id="toggle-optimized-router-button"
|
||||
isActive={!clientSideRouter}
|
||||
toggle={() => {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Routing',
|
||||
action: clientSideRouter ? 'enable routing API' : 'disable routing API',
|
||||
})
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { ThemedText } from '../../theme'
|
||||
|
||||
const Wrapper = styled.button<{ isActive?: boolean; activeElement?: boolean }>`
|
||||
border-radius: 20px;
|
||||
border: none;
|
||||
background: ${({ theme }) => theme.bg1};
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
padding: 0.4rem 0.4rem;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const ToggleElement = styled.span<{ isActive?: boolean; bgColor?: string }>`
|
||||
border-radius: 50%;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
background-color: ${({ isActive, bgColor, theme }) => (isActive ? bgColor : theme.bg4)};
|
||||
:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
`
|
||||
|
||||
const StatusText = styled(ThemedText.Main)<{ isActive?: boolean }>`
|
||||
margin: 0 10px;
|
||||
width: 24px;
|
||||
color: ${({ theme, isActive }) => (isActive ? theme.text1 : theme.text3)};
|
||||
`
|
||||
|
||||
interface ToggleProps {
|
||||
id?: string
|
||||
isActive: boolean
|
||||
bgColor: string
|
||||
toggle: () => void
|
||||
}
|
||||
|
||||
export default function ListToggle({ id, isActive, bgColor, toggle }: ToggleProps) {
|
||||
return (
|
||||
<Wrapper id={id} isActive={isActive} onClick={toggle}>
|
||||
{isActive && (
|
||||
<StatusText fontWeight="600" margin="0 6px" isActive={true}>
|
||||
<Trans>ON</Trans>
|
||||
</StatusText>
|
||||
)}
|
||||
<ToggleElement isActive={isActive} bgColor={bgColor} />
|
||||
{!isActive && (
|
||||
<StatusText fontWeight="600" margin="0 6px" isActive={false}>
|
||||
<Trans>OFF</Trans>
|
||||
</StatusText>
|
||||
)}
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
@@ -1,57 +1,85 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { darken } from 'polished'
|
||||
import { ReactNode } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { useState } from 'react'
|
||||
import styled, { keyframes } from 'styled-components/macro'
|
||||
|
||||
const ToggleElement = styled.span<{ isActive?: boolean; isOnSwitch?: boolean }>`
|
||||
padding: 0.25rem 0.6rem;
|
||||
border-radius: 9px;
|
||||
background: ${({ theme, isActive, isOnSwitch }) => (isActive ? (isOnSwitch ? theme.primary1 : theme.bg4) : 'none')};
|
||||
color: ${({ theme, isActive }) => (isActive ? theme.white : theme.text2)};
|
||||
font-size: 14px;
|
||||
font-weight: ${({ isOnSwitch }) => (isOnSwitch ? '500' : '400')};
|
||||
:hover {
|
||||
user-select: ${({ isOnSwitch }) => (isOnSwitch ? 'none' : 'initial')};
|
||||
background: ${({ theme, isActive, isOnSwitch }) =>
|
||||
isActive ? (isOnSwitch ? darken(0.05, theme.primary1) : darken(0.05, theme.bg4)) : 'none'};
|
||||
color: ${({ theme, isActive, isOnSwitch }) => (isActive ? (isOnSwitch ? theme.white : theme.white) : theme.text3)};
|
||||
const Wrapper = styled.button<{ isActive?: boolean; activeElement?: boolean }>`
|
||||
align-items: center;
|
||||
background: ${({ theme }) => theme.bg1};
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
outline: none;
|
||||
padding: 0.4rem 0.4rem;
|
||||
width: fit-content;
|
||||
`
|
||||
|
||||
const turnOnToggle = keyframes`
|
||||
from {
|
||||
margin-left: 0em;
|
||||
margin-right: 2.2em;
|
||||
}
|
||||
to {
|
||||
margin-left: 2.2em;
|
||||
margin-right: 0em;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledToggle = styled.button<{ isActive?: boolean; activeElement?: boolean }>`
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
background: ${({ theme }) => theme.bg0};
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
padding: 2px;
|
||||
const turnOffToggle = keyframes`
|
||||
from {
|
||||
margin-left: 2.2em;
|
||||
margin-right: 0em;
|
||||
}
|
||||
to {
|
||||
margin-left: 0em;
|
||||
margin-right: 2.2em;
|
||||
}
|
||||
`
|
||||
|
||||
const ToggleElementHoverStyle = (hasBgColor: boolean, theme: any, isActive?: boolean) =>
|
||||
hasBgColor
|
||||
? {
|
||||
opacity: '0.8',
|
||||
}
|
||||
: {
|
||||
background: isActive ? darken(0.05, theme.primary1) : darken(0.05, theme.bg4),
|
||||
color: isActive ? theme.white : theme.text3,
|
||||
}
|
||||
|
||||
const ToggleElement = styled.span<{ isActive?: boolean; bgColor?: string; isInitialToggleLoad?: boolean }>`
|
||||
animation: 0.1s
|
||||
${({ isActive, isInitialToggleLoad }) => (isInitialToggleLoad ? 'none' : isActive ? turnOnToggle : turnOffToggle)}
|
||||
ease-in;
|
||||
background: ${({ theme, bgColor, isActive }) =>
|
||||
isActive ? bgColor ?? theme.primary1 : !!bgColor ? theme.bg4 : theme.text3};
|
||||
border-radius: 50%;
|
||||
height: 24px;
|
||||
:hover {
|
||||
${({ bgColor, theme, isActive }) => ToggleElementHoverStyle(!!bgColor, theme, isActive)}
|
||||
}
|
||||
margin-left: ${({ isActive }) => (isActive ? '2.2em' : '0em')};
|
||||
margin-right: ${({ isActive }) => (!isActive ? '2.2em' : '0em')};
|
||||
width: 24px;
|
||||
`
|
||||
|
||||
interface ToggleProps {
|
||||
id?: string
|
||||
bgColor?: string
|
||||
isActive: boolean
|
||||
toggle: () => void
|
||||
checked?: ReactNode
|
||||
unchecked?: ReactNode
|
||||
}
|
||||
|
||||
export default function Toggle({
|
||||
id,
|
||||
isActive,
|
||||
toggle,
|
||||
checked = <Trans>On</Trans>,
|
||||
unchecked = <Trans>Off</Trans>,
|
||||
}: ToggleProps) {
|
||||
export default function Toggle({ id, bgColor, isActive, toggle }: ToggleProps) {
|
||||
const [isInitialToggleLoad, setIsInitialToggleLoad] = useState(true)
|
||||
|
||||
const switchToggle = () => {
|
||||
toggle()
|
||||
if (isInitialToggleLoad) setIsInitialToggleLoad(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledToggle id={id} isActive={isActive} onClick={toggle}>
|
||||
<ToggleElement isActive={isActive} isOnSwitch={true}>
|
||||
{checked}
|
||||
</ToggleElement>
|
||||
<ToggleElement isActive={!isActive} isOnSwitch={false}>
|
||||
{unchecked}
|
||||
</ToggleElement>
|
||||
</StyledToggle>
|
||||
<Wrapper id={id} isActive={isActive} onClick={switchToggle}>
|
||||
<ToggleElement isActive={isActive} bgColor={bgColor} isInitialToggleLoad={isInitialToggleLoad} />
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ export const TooltipContainer = styled.div`
|
||||
|
||||
interface TooltipProps extends Omit<PopoverProps, 'content'> {
|
||||
text: ReactNode
|
||||
disableHover?: boolean // disable the hover and content display
|
||||
}
|
||||
|
||||
interface TooltipContentProps extends Omit<PopoverProps, 'content'> {
|
||||
@@ -29,19 +30,20 @@ interface TooltipContentProps extends Omit<PopoverProps, 'content'> {
|
||||
}
|
||||
|
||||
export default function Tooltip({ text, ...rest }: TooltipProps) {
|
||||
return <Popover content={<TooltipContainer>{text}</TooltipContainer>} {...rest} />
|
||||
return <Popover content={text && <TooltipContainer>{text}</TooltipContainer>} {...rest} />
|
||||
}
|
||||
|
||||
function TooltipContent({ content, wrap = false, ...rest }: TooltipContentProps) {
|
||||
return <Popover content={wrap ? <TooltipContainer>{content}</TooltipContainer> : content} {...rest} />
|
||||
}
|
||||
|
||||
export function MouseoverTooltip({ children, ...rest }: Omit<TooltipProps, 'show'>) {
|
||||
/** Standard text tooltip. */
|
||||
export function MouseoverTooltip({ text, disableHover, children, ...rest }: Omit<TooltipProps, 'show'>) {
|
||||
const [show, setShow] = useState(false)
|
||||
const open = useCallback(() => setShow(true), [setShow])
|
||||
const close = useCallback(() => setShow(false), [setShow])
|
||||
return (
|
||||
<Tooltip {...rest} show={show}>
|
||||
<Tooltip {...rest} show={show} text={disableHover ? null : text}>
|
||||
<div onMouseEnter={open} onMouseLeave={close}>
|
||||
{children}
|
||||
</div>
|
||||
@@ -49,6 +51,7 @@ export function MouseoverTooltip({ children, ...rest }: Omit<TooltipProps, 'show
|
||||
)
|
||||
}
|
||||
|
||||
/** Tooltip that displays custom content. */
|
||||
export function MouseoverTooltipContent({
|
||||
content,
|
||||
children,
|
||||
|
||||
23
src/components/TopLevelModals/index.tsx
Normal file
23
src/components/TopLevelModals/index.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import AddressClaimModal from 'components/claim/AddressClaimModal'
|
||||
import ConnectedAccountBlocked from 'components/ConnectedAccountBlocked'
|
||||
import useAccountRiskCheck from 'hooks/useAccountRiskCheck'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useModalOpen, useToggleModal } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
|
||||
export default function TopLevelModals() {
|
||||
const addressClaimOpen = useModalOpen(ApplicationModal.ADDRESS_CLAIM)
|
||||
const addressClaimToggle = useToggleModal(ApplicationModal.ADDRESS_CLAIM)
|
||||
|
||||
const blockedAccountModalOpen = useModalOpen(ApplicationModal.BLOCKED_ACCOUNT)
|
||||
const { account } = useActiveWeb3React()
|
||||
|
||||
useAccountRiskCheck(account)
|
||||
const open = Boolean(blockedAccountModalOpen && account)
|
||||
return (
|
||||
<>
|
||||
<AddressClaimModal isOpen={addressClaimOpen} onDismiss={addressClaimToggle} />
|
||||
<ConnectedAccountBlocked account={account} isOpen={open} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { darken } from 'polished'
|
||||
import { ButtonEmpty, ButtonPrimary } from 'components/Button'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import { AbstractConnector } from 'web3-react-abstract-connector'
|
||||
|
||||
import { injected } from '../../connectors'
|
||||
import { SUPPORTED_WALLETS } from '../../constants/wallet'
|
||||
import Loader from '../Loader'
|
||||
import Option from './Option'
|
||||
|
||||
const PendingSection = styled.div`
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
@@ -18,18 +16,18 @@ const PendingSection = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const StyledLoader = styled(Loader)`
|
||||
margin-right: 1rem;
|
||||
`
|
||||
|
||||
const LoadingMessage = styled.div<{ error?: boolean }>`
|
||||
const LoaderContainer = styled.div`
|
||||
margin: 16px 0;
|
||||
${({ theme }) => theme.flexRowNoWrap};
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
const LoadingMessage = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap};
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 20px;
|
||||
color: ${({ theme, error }) => (error ? theme.red1 : 'inherit')};
|
||||
border: 1px solid ${({ theme, error }) => (error ? theme.red1 : theme.text4)};
|
||||
|
||||
& > * {
|
||||
padding: 1rem;
|
||||
@@ -37,29 +35,13 @@ const LoadingMessage = styled.div<{ error?: boolean }>`
|
||||
`
|
||||
|
||||
const ErrorGroup = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap};
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
`
|
||||
|
||||
const ErrorButton = styled.div`
|
||||
border-radius: 8px;
|
||||
font-size: 12px;
|
||||
color: ${({ theme }) => theme.text1};
|
||||
background-color: ${({ theme }) => theme.bg4};
|
||||
margin-left: 1rem;
|
||||
padding: 0.5rem;
|
||||
font-weight: 600;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: ${({ theme }) => darken(0.1, theme.text4)};
|
||||
}
|
||||
`
|
||||
|
||||
const LoadingWrapper = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap};
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`
|
||||
@@ -69,65 +51,56 @@ export default function PendingView({
|
||||
error = false,
|
||||
setPendingError,
|
||||
tryActivation,
|
||||
resetAccountView,
|
||||
}: {
|
||||
connector?: AbstractConnector
|
||||
error?: boolean
|
||||
setPendingError: (error: boolean) => void
|
||||
tryActivation: (connector: AbstractConnector) => void
|
||||
resetAccountView: () => void
|
||||
}) {
|
||||
const isMetamask = window?.ethereum?.isMetaMask
|
||||
|
||||
return (
|
||||
<PendingSection>
|
||||
<LoadingMessage error={error}>
|
||||
<LoadingMessage>
|
||||
<LoadingWrapper>
|
||||
{error ? (
|
||||
<ErrorGroup>
|
||||
<div>
|
||||
<ThemedText.MediumHeader marginBottom={12}>
|
||||
<Trans>Error connecting</Trans>
|
||||
</div>
|
||||
<ErrorButton
|
||||
</ThemedText.MediumHeader>
|
||||
<ThemedText.Body fontSize={14} marginBottom={36} textAlign="center">
|
||||
<Trans>
|
||||
The connection attempt failed. Please click try again and follow the steps to connect in your wallet.
|
||||
</Trans>
|
||||
</ThemedText.Body>
|
||||
<ButtonPrimary
|
||||
$borderRadius="12px"
|
||||
padding="12px"
|
||||
onClick={() => {
|
||||
setPendingError(false)
|
||||
connector && tryActivation(connector)
|
||||
}}
|
||||
>
|
||||
<Trans>Try Again</Trans>
|
||||
</ErrorButton>
|
||||
</ButtonPrimary>
|
||||
<ButtonEmpty width="fit-content" padding="0" marginTop={20}>
|
||||
<ThemedText.Link fontSize={12} onClick={resetAccountView}>
|
||||
<Trans>Back to wallet selection</Trans>
|
||||
</ThemedText.Link>
|
||||
</ButtonEmpty>
|
||||
</ErrorGroup>
|
||||
) : (
|
||||
<>
|
||||
<StyledLoader />
|
||||
<Trans>Initializing...</Trans>
|
||||
<ThemedText.Black fontSize={20} marginY={16}>
|
||||
<LoaderContainer>
|
||||
<Loader stroke="currentColor" size="32px" />
|
||||
</LoaderContainer>
|
||||
<Trans>Connecting...</Trans>
|
||||
</ThemedText.Black>
|
||||
</>
|
||||
)}
|
||||
</LoadingWrapper>
|
||||
</LoadingMessage>
|
||||
{Object.keys(SUPPORTED_WALLETS).map((key) => {
|
||||
const option = SUPPORTED_WALLETS[key]
|
||||
if (option.connector === connector) {
|
||||
if (option.connector === injected) {
|
||||
if (isMetamask && option.name !== 'MetaMask') {
|
||||
return null
|
||||
}
|
||||
if (!isMetamask && option.name === 'MetaMask') {
|
||||
return null
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Option
|
||||
id={`connect-${key}`}
|
||||
key={key}
|
||||
clickable={false}
|
||||
color={option.color}
|
||||
header={option.name}
|
||||
subheader={option.description}
|
||||
icon={option.iconURL}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return null
|
||||
})}
|
||||
</PendingSection>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { PrivacyPolicy } from 'components/PrivacyPolicy'
|
||||
import Row, { AutoRow, RowBetween } from 'components/Row'
|
||||
import { useWalletConnectMonitoringEventCallback } from 'hooks/useMonitoringEventCallback'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { ArrowLeft, ArrowRight, Info } from 'react-feather'
|
||||
import ReactGA from 'react-ga'
|
||||
import Row, { AutoRow } from 'components/Row'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { ArrowLeft } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { AbstractConnector } from 'web3-react-abstract-connector'
|
||||
import { UnsupportedChainIdError, useWeb3React } from 'web3-react-core'
|
||||
import { WalletConnectConnector } from 'web3-react-walletconnect-connector'
|
||||
|
||||
import MetamaskIcon from '../../assets/images/metamask.png'
|
||||
import TallyIcon from '../../assets/images/tally.png'
|
||||
import { ReactComponent as Close } from '../../assets/images/x.svg'
|
||||
import { fortmatic, injected, portis } from '../../connectors'
|
||||
import { fortmatic, injected } from '../../connectors'
|
||||
import { OVERLAY_READY } from '../../connectors/Fortmatic'
|
||||
import { SUPPORTED_WALLETS } from '../../constants/wallet'
|
||||
import usePrevious from '../../hooks/usePrevious'
|
||||
@@ -22,7 +22,7 @@ import { ApplicationModal } from '../../state/application/reducer'
|
||||
import { ExternalLink, ThemedText } from '../../theme'
|
||||
import { isMobile } from '../../utils/userAgent'
|
||||
import AccountDetails from '../AccountDetails'
|
||||
import Card, { LightCard } from '../Card'
|
||||
import { LightCard } from '../Card'
|
||||
import Modal from '../Modal'
|
||||
import Option from './Option'
|
||||
import PendingView from './PendingView'
|
||||
@@ -109,16 +109,6 @@ const HoverText = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const LinkCard = styled(Card)`
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
color: ${({ theme }) => theme.text3};
|
||||
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
`
|
||||
|
||||
const WALLET_VIEWS = {
|
||||
OPTIONS: 'options',
|
||||
OPTIONS_SECONDARY: 'options_secondary',
|
||||
@@ -137,7 +127,7 @@ export default function WalletModal({
|
||||
ENSName?: string
|
||||
}) {
|
||||
// important that these are destructed from the account-specific web3-react context
|
||||
const { active, account, connector, activate, error } = useWeb3React()
|
||||
const { account, connector, activate, error } = useWeb3React()
|
||||
|
||||
const [walletView, setWalletView] = useState(WALLET_VIEWS.ACCOUNT)
|
||||
const previousWalletView = usePrevious(walletView)
|
||||
@@ -151,7 +141,10 @@ export default function WalletModal({
|
||||
|
||||
const previousAccount = usePrevious(account)
|
||||
|
||||
const logMonitoringEvent = useWalletConnectMonitoringEventCallback()
|
||||
const resetAccountView = useCallback(() => {
|
||||
setPendingError(false)
|
||||
setWalletView(WALLET_VIEWS.ACCOUNT)
|
||||
}, [setPendingError, setWalletView])
|
||||
|
||||
// close on connection, when logged out before
|
||||
useEffect(() => {
|
||||
@@ -163,19 +156,9 @@ export default function WalletModal({
|
||||
// always reset to account view
|
||||
useEffect(() => {
|
||||
if (walletModalOpen) {
|
||||
setPendingError(false)
|
||||
setWalletView(WALLET_VIEWS.ACCOUNT)
|
||||
resetAccountView()
|
||||
}
|
||||
}, [walletModalOpen])
|
||||
|
||||
// close modal when a connection is successful
|
||||
const activePrevious = usePrevious(active)
|
||||
const connectorPrevious = usePrevious(connector)
|
||||
useEffect(() => {
|
||||
if (walletModalOpen && ((active && !activePrevious) || (connector && connector !== connectorPrevious && !error))) {
|
||||
setWalletView(WALLET_VIEWS.ACCOUNT)
|
||||
}
|
||||
}, [setWalletView, active, error, connector, walletModalOpen, activePrevious, connectorPrevious])
|
||||
}, [walletModalOpen, resetAccountView])
|
||||
|
||||
const tryActivation = async (connector: AbstractConnector | undefined) => {
|
||||
let name = ''
|
||||
@@ -186,7 +169,7 @@ export default function WalletModal({
|
||||
return true
|
||||
})
|
||||
// log selected wallet
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Wallet',
|
||||
action: 'Change Wallet',
|
||||
label: name,
|
||||
@@ -200,18 +183,13 @@ export default function WalletModal({
|
||||
}
|
||||
|
||||
connector &&
|
||||
activate(connector, undefined, true)
|
||||
.then(async () => {
|
||||
const walletAddress = await connector.getAccount()
|
||||
logMonitoringEvent({ walletAddress })
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error instanceof UnsupportedChainIdError) {
|
||||
activate(connector) // a little janky...can't use setError because the connector isn't set
|
||||
} else {
|
||||
setPendingError(true)
|
||||
}
|
||||
})
|
||||
activate(connector, undefined, true).catch((error) => {
|
||||
if (error instanceof UnsupportedChainIdError) {
|
||||
activate(connector) // a little janky...can't use setError because the connector isn't set
|
||||
} else {
|
||||
setPendingError(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// close wallet modal if fortmatic modal is active
|
||||
@@ -223,16 +201,12 @@ export default function WalletModal({
|
||||
|
||||
// get wallets user can switch too, depending on device/browser
|
||||
function getOptions() {
|
||||
const isMetamask = window.ethereum && window.ethereum.isMetaMask
|
||||
const isMetamask = !!window.ethereum?.isMetaMask
|
||||
const isTally = !!window.ethereum?.isTally
|
||||
return Object.keys(SUPPORTED_WALLETS).map((key) => {
|
||||
const option = SUPPORTED_WALLETS[key]
|
||||
// check for mobile options
|
||||
if (isMobile) {
|
||||
//disable portis on mobile for now
|
||||
if (option.connector === portis) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!window.web3 && !window.ethereum && option.mobile) {
|
||||
return (
|
||||
<Option
|
||||
@@ -280,6 +254,24 @@ export default function WalletModal({
|
||||
// likewise for generic
|
||||
else if (option.name === 'Injected' && isMetamask) {
|
||||
return null
|
||||
} else if (option.name === 'Injected' && isTally) {
|
||||
return (
|
||||
<Option
|
||||
id={`connect-${key}`}
|
||||
key={key}
|
||||
onClick={() => {
|
||||
option.connector === connector
|
||||
? setWalletView(WALLET_VIEWS.ACCOUNT)
|
||||
: !option.href && tryActivation(option.connector)
|
||||
}}
|
||||
color={'#E8831D'}
|
||||
header={<Trans>Tally</Trans>}
|
||||
active={option.connector === connector}
|
||||
subheader={null}
|
||||
link={null}
|
||||
icon={TallyIcon}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,12 +363,7 @@ export default function WalletModal({
|
||||
</CloseIcon>
|
||||
{walletView !== WALLET_VIEWS.ACCOUNT ? (
|
||||
<HeaderRow color="blue">
|
||||
<HoverText
|
||||
onClick={() => {
|
||||
setPendingError(false)
|
||||
setWalletView(WALLET_VIEWS.ACCOUNT)
|
||||
}}
|
||||
>
|
||||
<HoverText onClick={resetAccountView}>
|
||||
<ArrowLeft />
|
||||
</HoverText>
|
||||
</HeaderRow>
|
||||
@@ -390,39 +377,38 @@ export default function WalletModal({
|
||||
|
||||
<ContentWrapper>
|
||||
<AutoColumn gap="16px">
|
||||
<LightCard>
|
||||
<AutoRow style={{ flexWrap: 'nowrap' }}>
|
||||
<ThemedText.Black fontSize={14}>
|
||||
<Trans>
|
||||
By connecting a wallet, you agree to Uniswap Labs’{' '}
|
||||
<ExternalLink href="https://uniswap.org/terms-of-service/">Terms of Service</ExternalLink> and
|
||||
acknowledge that you have read and understand the Uniswap{' '}
|
||||
<ExternalLink href="https://uniswap.org/disclaimer/">Protocol Disclaimer</ExternalLink>.
|
||||
</Trans>
|
||||
</ThemedText.Black>
|
||||
</AutoRow>
|
||||
</LightCard>
|
||||
{walletView === WALLET_VIEWS.PENDING ? (
|
||||
{walletView === WALLET_VIEWS.PENDING && (
|
||||
<PendingView
|
||||
connector={pendingWallet}
|
||||
error={pendingError}
|
||||
setPendingError={setPendingError}
|
||||
tryActivation={tryActivation}
|
||||
resetAccountView={resetAccountView}
|
||||
/>
|
||||
) : (
|
||||
<OptionGrid>{getOptions()}</OptionGrid>
|
||||
)}
|
||||
<LinkCard padding=".5rem" $borderRadius=".75rem" onClick={() => setWalletView(WALLET_VIEWS.LEGAL)}>
|
||||
<RowBetween>
|
||||
<AutoRow gap="4px">
|
||||
<Info size={20} />
|
||||
<ThemedText.Label fontSize={14}>
|
||||
<Trans>How this app uses APIs</Trans>
|
||||
</ThemedText.Label>
|
||||
{walletView !== WALLET_VIEWS.PENDING && <OptionGrid>{getOptions()}</OptionGrid>}
|
||||
{!pendingError && (
|
||||
<LightCard>
|
||||
<AutoRow style={{ flexWrap: 'nowrap' }}>
|
||||
<ThemedText.Body fontSize={12}>
|
||||
<Trans>
|
||||
By connecting a wallet, you agree to Uniswap Labs’{' '}
|
||||
<ExternalLink
|
||||
style={{ textDecoration: 'underline' }}
|
||||
href="https://uniswap.org/terms-of-service/"
|
||||
>
|
||||
Terms of Service
|
||||
</ExternalLink>{' '}
|
||||
and acknowledge that you have read and understand the Uniswap{' '}
|
||||
<ExternalLink style={{ textDecoration: 'underline' }} href="https://uniswap.org/disclaimer/">
|
||||
Protocol Disclaimer
|
||||
</ExternalLink>
|
||||
.
|
||||
</Trans>
|
||||
</ThemedText.Body>
|
||||
</AutoRow>
|
||||
<ArrowRight size={16} />
|
||||
</RowBetween>
|
||||
</LinkCard>
|
||||
</LightCard>
|
||||
)}
|
||||
</AutoColumn>
|
||||
</ContentWrapper>
|
||||
</UpperSection>
|
||||
|
||||
@@ -13,7 +13,7 @@ import useENSName from '../../hooks/useENSName'
|
||||
import { useHasSocks } from '../../hooks/useSocksBalance'
|
||||
import { useWalletModalToggle } from '../../state/application/hooks'
|
||||
import { isTransactionRecent, useAllTransactions } from '../../state/transactions/hooks'
|
||||
import { TransactionDetails } from '../../state/transactions/reducer'
|
||||
import { TransactionDetails } from '../../state/transactions/types'
|
||||
import { shortenAddress } from '../../utils'
|
||||
import { ButtonSecondary } from '../Button'
|
||||
import StatusIcon from '../Identicon/StatusIcon'
|
||||
|
||||
51
src/components/analytics/GoogleAnalyticsProvider.tsx
Normal file
51
src/components/analytics/GoogleAnalyticsProvider.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import ReactGA from 'react-ga4'
|
||||
import { GaOptions, InitOptions, UaEventOptions } from 'react-ga4/types/ga4'
|
||||
|
||||
/**
|
||||
* Google Analytics Provider containing all methods used throughout app to log events to Google Analytics.
|
||||
*/
|
||||
export default class GoogleAnalyticsProvider {
|
||||
public sendEvent(event: string | UaEventOptions, params?: any) {
|
||||
ReactGA.event(event, params)
|
||||
}
|
||||
|
||||
public initialize(
|
||||
GA_MEASUREMENT_ID: InitOptions[] | string,
|
||||
options?: {
|
||||
legacyDimensionMetric?: boolean
|
||||
nonce?: string
|
||||
testMode?: boolean
|
||||
gaOptions?: GaOptions | any
|
||||
gtagOptions?: any
|
||||
}
|
||||
) {
|
||||
ReactGA.initialize(GA_MEASUREMENT_ID, options)
|
||||
}
|
||||
|
||||
public set(fieldsObject: any) {
|
||||
ReactGA.set(fieldsObject)
|
||||
}
|
||||
|
||||
public outboundLink(
|
||||
{
|
||||
label,
|
||||
}: {
|
||||
label: string
|
||||
},
|
||||
hitCallback: () => unknown
|
||||
) {
|
||||
ReactGA.outboundLink({ label }, hitCallback)
|
||||
}
|
||||
|
||||
public pageview(path?: string, _?: string[], title?: string) {
|
||||
ReactGA.pageview(path, _, title)
|
||||
}
|
||||
|
||||
public ga(...args: any[]) {
|
||||
ReactGA.ga(...args)
|
||||
}
|
||||
|
||||
public gaCommandSendTiming(timingCategory: any, timingVar: any, timingValue: any, timingLabel: any) {
|
||||
ReactGA._gaCommandSendTiming(timingCategory, timingVar, timingValue, timingLabel)
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useEffect } from 'react'
|
||||
import ReactGA from 'react-ga'
|
||||
import { RouteComponentProps } from 'react-router-dom'
|
||||
import { getCLS, getFCP, getFID, getLCP, Metric } from 'web-vitals'
|
||||
|
||||
import { GOOGLE_ANALYTICS_CLIENT_ID_STORAGE_KEY } from './index'
|
||||
|
||||
function reportWebVitals({ name, delta, id }: Metric) {
|
||||
ReactGA.timing({
|
||||
category: 'Web Vitals',
|
||||
variable: name,
|
||||
value: Math.round(name === 'CLS' ? delta * 1000 : delta),
|
||||
label: id,
|
||||
})
|
||||
}
|
||||
|
||||
// tracks web vitals and pageviews
|
||||
export default function GoogleAnalyticsReporter({ location: { pathname, search } }: RouteComponentProps): null {
|
||||
useEffect(() => {
|
||||
getFCP(reportWebVitals)
|
||||
getFID(reportWebVitals)
|
||||
getLCP(reportWebVitals)
|
||||
getCLS(reportWebVitals)
|
||||
}, [])
|
||||
|
||||
const { chainId } = useActiveWeb3React()
|
||||
useEffect(() => {
|
||||
// cd1 - custom dimension 1 - chainId
|
||||
ReactGA.set({ cd1: chainId ?? 0 })
|
||||
}, [chainId])
|
||||
|
||||
useEffect(() => {
|
||||
ReactGA.pageview(`${pathname}${search}`)
|
||||
}, [pathname, search])
|
||||
|
||||
useEffect(() => {
|
||||
// typed as 'any' in react-ga -.-
|
||||
ReactGA.ga((tracker: any) => {
|
||||
if (!tracker) return
|
||||
|
||||
const clientId = tracker.get('clientId')
|
||||
window.localStorage.setItem(GOOGLE_ANALYTICS_CLIENT_ID_STORAGE_KEY, clientId)
|
||||
})
|
||||
}, [])
|
||||
return null
|
||||
}
|
||||
@@ -1,20 +1,47 @@
|
||||
import ReactGA from 'react-ga'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useEffect } from 'react'
|
||||
import { UaEventOptions } from 'react-ga4/types/ga4'
|
||||
import { RouteComponentProps } from 'react-router-dom'
|
||||
import { isMobile } from 'utils/userAgent'
|
||||
import { getCLS, getFCP, getFID, getLCP, Metric } from 'web-vitals'
|
||||
|
||||
import GoogleAnalyticsProvider from './GoogleAnalyticsProvider'
|
||||
|
||||
export const GOOGLE_ANALYTICS_CLIENT_ID_STORAGE_KEY = 'ga_client_id'
|
||||
const GOOGLE_ANALYTICS_ID: string | undefined = process.env.REACT_APP_GOOGLE_ANALYTICS_ID
|
||||
|
||||
const storedClientId = window.localStorage.getItem(GOOGLE_ANALYTICS_CLIENT_ID_STORAGE_KEY)
|
||||
|
||||
const googleAnalytics = new GoogleAnalyticsProvider()
|
||||
|
||||
export function sendEvent(event: string | UaEventOptions, params?: any) {
|
||||
return googleAnalytics.sendEvent(event, params)
|
||||
}
|
||||
|
||||
export function outboundLink(
|
||||
{
|
||||
label,
|
||||
}: {
|
||||
label: string
|
||||
},
|
||||
hitCallback: () => unknown
|
||||
) {
|
||||
return googleAnalytics.outboundLink({ label }, hitCallback)
|
||||
}
|
||||
|
||||
export function sendTiming(timingCategory: any, timingVar: any, timingValue: any, timingLabel: any) {
|
||||
return googleAnalytics.gaCommandSendTiming(timingCategory, timingVar, timingValue, timingLabel)
|
||||
}
|
||||
|
||||
if (typeof GOOGLE_ANALYTICS_ID === 'string') {
|
||||
ReactGA.initialize(GOOGLE_ANALYTICS_ID, {
|
||||
googleAnalytics.initialize(GOOGLE_ANALYTICS_ID, {
|
||||
gaOptions: {
|
||||
storage: 'none',
|
||||
storeGac: false,
|
||||
clientId: storedClientId ?? undefined,
|
||||
},
|
||||
})
|
||||
ReactGA.set({
|
||||
googleAnalytics.set({
|
||||
anonymizeIp: true,
|
||||
customBrowserType: !isMobile
|
||||
? 'desktop'
|
||||
@@ -23,5 +50,44 @@ if (typeof GOOGLE_ANALYTICS_ID === 'string') {
|
||||
: 'mobileRegular',
|
||||
})
|
||||
} else {
|
||||
ReactGA.initialize('test', { testMode: true, debug: true })
|
||||
googleAnalytics.initialize('test', { gtagOptions: { debug_mode: true } })
|
||||
}
|
||||
|
||||
const installed = Boolean(window.navigator.serviceWorker?.controller)
|
||||
const hit = Boolean((window as any).__isDocumentCached)
|
||||
const action = installed ? (hit ? 'Cache hit' : 'Cache miss') : 'Not installed'
|
||||
sendEvent({ category: 'Service Worker', action, nonInteraction: true })
|
||||
|
||||
function reportWebVitals({ name, delta, id }: Metric) {
|
||||
sendTiming('Web Vitals', name, Math.round(name === 'CLS' ? delta * 1000 : delta), id)
|
||||
}
|
||||
|
||||
// tracks web vitals and pageviews
|
||||
export function useAnalyticsReporter({ pathname, search }: RouteComponentProps['location']) {
|
||||
useEffect(() => {
|
||||
getFCP(reportWebVitals)
|
||||
getFID(reportWebVitals)
|
||||
getLCP(reportWebVitals)
|
||||
getCLS(reportWebVitals)
|
||||
}, [])
|
||||
|
||||
const { chainId } = useActiveWeb3React()
|
||||
useEffect(() => {
|
||||
// cd1 - custom dimension 1 - chainId
|
||||
googleAnalytics.set({ cd1: chainId ?? 0 })
|
||||
}, [chainId])
|
||||
|
||||
useEffect(() => {
|
||||
googleAnalytics.pageview(`${pathname}${search}`)
|
||||
}, [pathname, search])
|
||||
|
||||
useEffect(() => {
|
||||
// typed as 'any' in react-ga4 -.-
|
||||
googleAnalytics.ga((tracker: any) => {
|
||||
if (!tracker) return
|
||||
|
||||
const clientId = tracker.get('clientId')
|
||||
window.localStorage.setItem(GOOGLE_ANALYTICS_CLIENT_ID_STORAGE_KEY, clientId)
|
||||
})
|
||||
}, [])
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import styled from 'styled-components/macro'
|
||||
|
||||
import { useContract } from '../../hooks/useContract'
|
||||
import { StakingInfo } from '../../state/stake/hooks'
|
||||
import { TransactionType } from '../../state/transactions/actions'
|
||||
import { useTransactionAdder } from '../../state/transactions/hooks'
|
||||
import { TransactionType } from '../../state/transactions/types'
|
||||
import { CloseIcon, ThemedText } from '../../theme'
|
||||
import { ButtonError } from '../Button'
|
||||
import { AutoColumn } from '../Column'
|
||||
|
||||
@@ -12,8 +12,8 @@ import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallbac
|
||||
import { useContract, usePairContract, useV2RouterContract } from '../../hooks/useContract'
|
||||
import useTransactionDeadline from '../../hooks/useTransactionDeadline'
|
||||
import { StakingInfo, useDerivedStakeInfo } from '../../state/stake/hooks'
|
||||
import { TransactionType } from '../../state/transactions/actions'
|
||||
import { useTransactionAdder } from '../../state/transactions/hooks'
|
||||
import { TransactionType } from '../../state/transactions/types'
|
||||
import { CloseIcon, ThemedText } from '../../theme'
|
||||
import { formatCurrencyAmount } from '../../utils/formatCurrencyAmount'
|
||||
import { maxAmountSpend } from '../../utils/maxAmountSpend'
|
||||
|
||||
@@ -7,8 +7,8 @@ import styled from 'styled-components/macro'
|
||||
|
||||
import { useContract } from '../../hooks/useContract'
|
||||
import { StakingInfo } from '../../state/stake/hooks'
|
||||
import { TransactionType } from '../../state/transactions/actions'
|
||||
import { useTransactionAdder } from '../../state/transactions/hooks'
|
||||
import { TransactionType } from '../../state/transactions/types'
|
||||
import { CloseIcon, ThemedText } from '../../theme'
|
||||
import { ButtonError } from '../Button'
|
||||
import { AutoColumn } from '../Column'
|
||||
@@ -42,7 +42,7 @@ export default function UnstakingModal({ isOpen, onDismiss, stakingInfo }: Staki
|
||||
const [hash, setHash] = useState<string | undefined>()
|
||||
const [attempting, setAttempting] = useState(false)
|
||||
|
||||
function wrappedOndismiss() {
|
||||
function wrappedOnDismiss() {
|
||||
setHash(undefined)
|
||||
setAttempting(false)
|
||||
onDismiss()
|
||||
@@ -79,14 +79,14 @@ export default function UnstakingModal({ isOpen, onDismiss, stakingInfo }: Staki
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onDismiss={wrappedOndismiss} maxHeight={90}>
|
||||
<Modal isOpen={isOpen} onDismiss={wrappedOnDismiss} maxHeight={90}>
|
||||
{!attempting && !hash && (
|
||||
<ContentWrapper gap="lg">
|
||||
<RowBetween>
|
||||
<ThemedText.MediumHeader>
|
||||
<Trans>Withdraw</Trans>
|
||||
</ThemedText.MediumHeader>
|
||||
<CloseIcon onClick={wrappedOndismiss} />
|
||||
<CloseIcon onClick={wrappedOnDismiss} />
|
||||
</RowBetween>
|
||||
{stakingInfo?.stakedAmount && (
|
||||
<AutoColumn justify="center" gap="md">
|
||||
@@ -117,7 +117,7 @@ export default function UnstakingModal({ isOpen, onDismiss, stakingInfo }: Staki
|
||||
</ContentWrapper>
|
||||
)}
|
||||
{attempting && !hash && (
|
||||
<LoadingView onDismiss={wrappedOndismiss}>
|
||||
<LoadingView onDismiss={wrappedOnDismiss}>
|
||||
<AutoColumn gap="12px" justify={'center'}>
|
||||
<ThemedText.Body fontSize={20}>
|
||||
<Trans>Withdrawing {stakingInfo?.stakedAmount?.toSignificant(4)} UNI-V2</Trans>
|
||||
@@ -129,7 +129,7 @@ export default function UnstakingModal({ isOpen, onDismiss, stakingInfo }: Staki
|
||||
</LoadingView>
|
||||
)}
|
||||
{hash && (
|
||||
<SubmittedView onDismiss={wrappedOndismiss} hash={hash}>
|
||||
<SubmittedView onDismiss={wrappedOnDismiss} hash={hash}>
|
||||
<AutoColumn gap="12px" justify={'center'}>
|
||||
<ThemedText.LargeHeader>
|
||||
<Trans>Transaction Submitted</Trans>
|
||||
|
||||
@@ -4,6 +4,7 @@ import Card from 'components/Card'
|
||||
import { LoadingRows } from 'components/Loader/styled'
|
||||
import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
import { useContext, useMemo } from 'react'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
@@ -12,6 +13,7 @@ import { Separator, ThemedText } from '../../theme'
|
||||
import { computeRealizedLPFeePercent } from '../../utils/prices'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
import { MouseoverTooltip } from '../Tooltip'
|
||||
import FormattedPriceImpact from './FormattedPriceImpact'
|
||||
|
||||
const StyledCard = styled(Card)`
|
||||
@@ -23,6 +25,7 @@ interface AdvancedSwapDetailsProps {
|
||||
allowedSlippage: Percent
|
||||
syncing?: boolean
|
||||
hideRouteDiagram?: boolean
|
||||
hideInfoTooltips?: boolean
|
||||
}
|
||||
|
||||
function TextWithLoadingPlaceholder({
|
||||
@@ -43,9 +46,15 @@ function TextWithLoadingPlaceholder({
|
||||
)
|
||||
}
|
||||
|
||||
export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }: AdvancedSwapDetailsProps) {
|
||||
export function AdvancedSwapDetails({
|
||||
trade,
|
||||
allowedSlippage,
|
||||
syncing = false,
|
||||
hideInfoTooltips = false,
|
||||
}: AdvancedSwapDetailsProps) {
|
||||
const theme = useContext(ThemeContext)
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const nativeCurrency = useNativeCurrency()
|
||||
|
||||
const { expectedOutputAmount, priceImpact } = useMemo(() => {
|
||||
if (!trade) return { expectedOutputAmount: undefined, priceImpact: undefined }
|
||||
@@ -60,9 +69,19 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }:
|
||||
<AutoColumn gap="8px">
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<ThemedText.SubHeader color={theme.text1}>
|
||||
<Trans>Expected Output</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
<MouseoverTooltip
|
||||
text={
|
||||
<Trans>
|
||||
The amount you expect to receive at the current market price. You may receive less or more if the
|
||||
market price changes while your transaction is pending.
|
||||
</Trans>
|
||||
}
|
||||
disableHover={hideInfoTooltips}
|
||||
>
|
||||
<ThemedText.SubHeader color={theme.text1}>
|
||||
<Trans>Expected Output</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
</MouseoverTooltip>
|
||||
</RowFixed>
|
||||
<TextWithLoadingPlaceholder syncing={syncing} width={65}>
|
||||
<ThemedText.Black textAlign="right" fontSize={14}>
|
||||
@@ -74,9 +93,14 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }:
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<ThemedText.SubHeader color={theme.text1}>
|
||||
<Trans>Price Impact</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
<MouseoverTooltip
|
||||
text={<Trans>The impact your trade has on the market price of this pool.</Trans>}
|
||||
disableHover={hideInfoTooltips}
|
||||
>
|
||||
<ThemedText.SubHeader color={theme.text1}>
|
||||
<Trans>Price Impact</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
</MouseoverTooltip>
|
||||
</RowFixed>
|
||||
<TextWithLoadingPlaceholder syncing={syncing} width={50}>
|
||||
<ThemedText.Black textAlign="right" fontSize={14}>
|
||||
@@ -87,14 +111,24 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }:
|
||||
<Separator />
|
||||
<RowBetween>
|
||||
<RowFixed style={{ marginRight: '20px' }}>
|
||||
<ThemedText.SubHeader color={theme.text3}>
|
||||
{trade.tradeType === TradeType.EXACT_INPUT ? (
|
||||
<Trans>Minimum received</Trans>
|
||||
) : (
|
||||
<Trans>Maximum sent</Trans>
|
||||
)}{' '}
|
||||
<Trans>after slippage</Trans> ({allowedSlippage.toFixed(2)}%)
|
||||
</ThemedText.SubHeader>
|
||||
<MouseoverTooltip
|
||||
text={
|
||||
<Trans>
|
||||
The minimum amount you are guaranteed to receive. If the price slips any further, your transaction
|
||||
will revert.
|
||||
</Trans>
|
||||
}
|
||||
disableHover={hideInfoTooltips}
|
||||
>
|
||||
<ThemedText.SubHeader color={theme.text3}>
|
||||
{trade.tradeType === TradeType.EXACT_INPUT ? (
|
||||
<Trans>Minimum received</Trans>
|
||||
) : (
|
||||
<Trans>Maximum sent</Trans>
|
||||
)}{' '}
|
||||
<Trans>after slippage</Trans> ({allowedSlippage.toFixed(2)}%)
|
||||
</ThemedText.SubHeader>
|
||||
</MouseoverTooltip>
|
||||
</RowFixed>
|
||||
<TextWithLoadingPlaceholder syncing={syncing} width={70}>
|
||||
<ThemedText.Black textAlign="right" fontSize={14} color={theme.text3}>
|
||||
@@ -106,9 +140,18 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }:
|
||||
</RowBetween>
|
||||
{!trade?.gasUseEstimateUSD || !chainId || !SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId) ? null : (
|
||||
<RowBetween>
|
||||
<ThemedText.SubHeader color={theme.text3}>
|
||||
<Trans>Network Fee</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
<MouseoverTooltip
|
||||
text={
|
||||
<Trans>
|
||||
The fee paid to miners who process your transaction. This must be paid in {nativeCurrency.symbol}.
|
||||
</Trans>
|
||||
}
|
||||
disableHover={hideInfoTooltips}
|
||||
>
|
||||
<ThemedText.SubHeader color={theme.text3}>
|
||||
<Trans>Network Fee</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
</MouseoverTooltip>
|
||||
<TextWithLoadingPlaceholder syncing={syncing} width={50}>
|
||||
<ThemedText.Black textAlign="right" fontSize={14} color={theme.text3}>
|
||||
~${trade.gasUseEstimateUSD.toFixed(2)}
|
||||
|
||||
35
src/components/swap/AutoRouterIcon.tsx
Normal file
35
src/components/swap/AutoRouterIcon.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { useRef } from 'react'
|
||||
|
||||
let uniqueId = 0
|
||||
const getUniqueId = () => uniqueId++
|
||||
|
||||
export default function AutoRouterIcon({ className, id }: { className?: string; id?: string }) {
|
||||
const componentIdRef = useRef(id ?? getUniqueId())
|
||||
const componentId = `AutoRouterIconGradient${componentIdRef.current}`
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="23"
|
||||
height="20"
|
||||
viewBox="0 0 23 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id={componentId} x1="0" y1="0" x2="1" y2="0" gradientTransform="rotate(95)">
|
||||
<stop id="stop1" offset="0" stopColor="#2274E2" />
|
||||
<stop id="stop1" offset="0.5" stopColor="#2274E2" />
|
||||
<stop id="stop2" offset="1" stopColor="#3FB672" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
d="M16 16C10 16 9 10 5 10M16 16C16 17.6569 17.3431 19 19 19C20.6569 19 22 17.6569 22 16C22 14.3431 20.6569 13 19 13C17.3431 13 16 14.3431 16 16ZM5 10C9 10 10 4 16 4M5 10H1.5M16 4C16 5.65685 17.3431 7 19 7C20.6569 7 22 5.65685 22 4C22 2.34315 20.6569 1 19 1C17.3431 1 16 2.34315 16 4Z"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
stroke={`url(#${componentId})`}
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, TradeType } from '@uniswap/sdk-core'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { LoadingOpacityContainer } from 'components/Loader/styled'
|
||||
import { RowFixed } from 'components/Row'
|
||||
import { MouseoverTooltipContent } from 'components/Tooltip'
|
||||
import ReactGA from 'react-ga'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
@@ -85,7 +85,7 @@ export default function GasEstimateBadge({
|
||||
}
|
||||
placement="bottom"
|
||||
onOpen={() =>
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Gas',
|
||||
action: 'Gas Details Tooltip Open',
|
||||
})
|
||||
|
||||
@@ -3,8 +3,8 @@ import useAutoRouterSupported from 'hooks/useAutoRouterSupported'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
|
||||
import { ReactComponent as AutoRouterIcon } from '../../assets/svg/auto_router.svg'
|
||||
import { ReactComponent as StaticRouterIcon } from '../../assets/svg/static_route.svg'
|
||||
import AutoRouterIcon from './AutoRouterIcon'
|
||||
|
||||
const StyledAutoRouterIcon = styled(AutoRouterIcon)`
|
||||
height: 16px;
|
||||
|
||||
@@ -147,7 +147,12 @@ export default function SwapDetailsDropdown({
|
||||
content={
|
||||
<ResponsiveTooltipContainer origin="top right" style={{ padding: '0' }}>
|
||||
<Card padding="12px">
|
||||
<AdvancedSwapDetails trade={trade} allowedSlippage={allowedSlippage} syncing={syncing} />
|
||||
<AdvancedSwapDetails
|
||||
trade={trade}
|
||||
allowedSlippage={allowedSlippage}
|
||||
syncing={syncing}
|
||||
hideInfoTooltips={true}
|
||||
/>
|
||||
</Card>
|
||||
</ResponsiveTooltipContainer>
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, TradeType } from '@uniswap/sdk-core'
|
||||
import { Protocol } from '@uniswap/router-sdk'
|
||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import AnimatedDropdown from 'components/AnimatedDropdown'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { LoadingRows } from 'components/Loader/styled'
|
||||
@@ -8,7 +11,6 @@ import { AutoRow, RowBetween } from 'components/Row'
|
||||
import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import useAutoRouterSupported from 'hooks/useAutoRouterSupported'
|
||||
import { getTokenPath } from 'lib/components/Swap/RoutingDiagram/utils'
|
||||
import { memo, useState } from 'react'
|
||||
import { Plus } from 'react-feather'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
@@ -106,3 +108,41 @@ export default memo(function SwapRoute({ trade, syncing, fixedOpen = false, ...r
|
||||
</Wrapper>
|
||||
)
|
||||
})
|
||||
|
||||
export interface RoutingDiagramEntry {
|
||||
percent: Percent
|
||||
path: [Currency, Currency, FeeAmount][]
|
||||
protocol: Protocol
|
||||
}
|
||||
|
||||
const V2_DEFAULT_FEE_TIER = 3000
|
||||
|
||||
/**
|
||||
* Loops through all routes on a trade and returns an array of diagram entries.
|
||||
*/
|
||||
export function getTokenPath(trade: InterfaceTrade<Currency, Currency, TradeType>): RoutingDiagramEntry[] {
|
||||
return trade.swaps.map(({ route: { path: tokenPath, pools, protocol }, inputAmount, outputAmount }) => {
|
||||
const portion =
|
||||
trade.tradeType === TradeType.EXACT_INPUT
|
||||
? inputAmount.divide(trade.inputAmount)
|
||||
: outputAmount.divide(trade.outputAmount)
|
||||
const percent = new Percent(portion.numerator, portion.denominator)
|
||||
const path: RoutingDiagramEntry['path'] = []
|
||||
for (let i = 0; i < pools.length; i++) {
|
||||
const nextPool = pools[i]
|
||||
const tokenIn = tokenPath[i]
|
||||
const tokenOut = tokenPath[i + 1]
|
||||
const entry: RoutingDiagramEntry['path'][0] = [
|
||||
tokenIn,
|
||||
tokenOut,
|
||||
nextPool instanceof Pair ? V2_DEFAULT_FEE_TIER : nextPool.fee,
|
||||
]
|
||||
path.push(entry)
|
||||
}
|
||||
return {
|
||||
percent,
|
||||
path,
|
||||
protocol,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -33,6 +33,11 @@ export default function TradePrice({ price, showInverted, setShowInverted }: Tra
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
const usdcPrice = useUSDCPrice(showInverted ? price.baseCurrency : price.quoteCurrency)
|
||||
/*
|
||||
* calculate needed amount of decimal prices, for prices between 0.95-1.05 use 4 decimal places
|
||||
*/
|
||||
const p = Number(usdcPrice?.toFixed())
|
||||
const visibleDecimalPlaces = p < 1.05 ? 4 : 2
|
||||
|
||||
let formattedPrice: string
|
||||
try {
|
||||
@@ -60,7 +65,7 @@ export default function TradePrice({ price, showInverted, setShowInverted }: Tra
|
||||
</Text>{' '}
|
||||
{usdcPrice && (
|
||||
<ThemedText.DarkGray>
|
||||
<Trans>(${usdcPrice.toSignificant(6, { groupSeparator: ',' })})</Trans>
|
||||
<Trans>(${usdcPrice.toFixed(visibleDecimalPlaces, { groupSeparator: ',' })})</Trans>
|
||||
</ThemedText.DarkGray>
|
||||
)}
|
||||
</StyledPriceContainer>
|
||||
|
||||
@@ -66,7 +66,7 @@ export default function DelegateModal({ isOpen, onDismiss, title }: VoteModalPro
|
||||
const [attempting, setAttempting] = useState(false)
|
||||
|
||||
// wrapper to reset state on modal close
|
||||
function wrappedOndismiss() {
|
||||
function wrappedOnDismiss() {
|
||||
setHash(undefined)
|
||||
setAttempting(false)
|
||||
onDismiss()
|
||||
@@ -90,13 +90,13 @@ export default function DelegateModal({ isOpen, onDismiss, title }: VoteModalPro
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onDismiss={wrappedOndismiss} maxHeight={90}>
|
||||
<Modal isOpen={isOpen} onDismiss={wrappedOnDismiss} maxHeight={90}>
|
||||
{!attempting && !hash && (
|
||||
<ContentWrapper gap="lg">
|
||||
<AutoColumn gap="lg" justify="center">
|
||||
<RowBetween>
|
||||
<ThemedText.MediumHeader fontWeight={500}>{title}</ThemedText.MediumHeader>
|
||||
<StyledClosed stroke="black" onClick={wrappedOndismiss} />
|
||||
<StyledClosed stroke="black" onClick={wrappedOnDismiss} />
|
||||
</RowBetween>
|
||||
<ThemedText.Body>
|
||||
<Trans>Earned UNI tokens represent voting shares in Uniswap governance.</Trans>
|
||||
@@ -119,7 +119,7 @@ export default function DelegateModal({ isOpen, onDismiss, title }: VoteModalPro
|
||||
</ContentWrapper>
|
||||
)}
|
||||
{attempting && !hash && (
|
||||
<LoadingView onDismiss={wrappedOndismiss}>
|
||||
<LoadingView onDismiss={wrappedOnDismiss}>
|
||||
<AutoColumn gap="12px" justify={'center'}>
|
||||
<ThemedText.LargeHeader>
|
||||
{usingDelegate ? <Trans>Delegating votes</Trans> : <Trans>Unlocking Votes</Trans>}
|
||||
@@ -129,7 +129,7 @@ export default function DelegateModal({ isOpen, onDismiss, title }: VoteModalPro
|
||||
</LoadingView>
|
||||
)}
|
||||
{hash && (
|
||||
<SubmittedView onDismiss={wrappedOndismiss} hash={hash}>
|
||||
<SubmittedView onDismiss={wrappedOnDismiss} hash={hash}>
|
||||
<AutoColumn gap="12px" justify={'center'}>
|
||||
<ThemedText.LargeHeader>
|
||||
<Trans>Transaction Submitted</Trans>
|
||||
|
||||
153
src/components/vote/ExecuteModal.tsx
Normal file
153
src/components/vote/ExecuteModal.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useContext, useState } from 'react'
|
||||
import { ArrowUpCircle, X } from 'react-feather'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
|
||||
import Circle from '../../assets/images/blue-loader.svg'
|
||||
import { useExecuteCallback } from '../../state/governance/hooks'
|
||||
import { CustomLightSpinner, ThemedText } from '../../theme'
|
||||
import { ExternalLink } from '../../theme'
|
||||
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
|
||||
import { ButtonPrimary } from '../Button'
|
||||
import { AutoColumn, ColumnCenter } from '../Column'
|
||||
import Modal from '../Modal'
|
||||
import { RowBetween } from '../Row'
|
||||
|
||||
const ContentWrapper = styled(AutoColumn)`
|
||||
width: 100%;
|
||||
padding: 24px;
|
||||
`
|
||||
|
||||
const StyledClosed = styled(X)`
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
|
||||
const ConfirmOrLoadingWrapper = styled.div`
|
||||
width: 100%;
|
||||
padding: 24px;
|
||||
`
|
||||
|
||||
const ConfirmedIcon = styled(ColumnCenter)`
|
||||
padding: 60px 0;
|
||||
`
|
||||
|
||||
interface ExecuteModalProps {
|
||||
isOpen: boolean
|
||||
onDismiss: () => void
|
||||
proposalId: string | undefined // id for the proposal to execute
|
||||
}
|
||||
|
||||
export default function ExecuteModal({ isOpen, onDismiss, proposalId }: ExecuteModalProps) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const executeCallback = useExecuteCallback()
|
||||
|
||||
// monitor call to help UI loading state
|
||||
const [hash, setHash] = useState<string | undefined>()
|
||||
const [attempting, setAttempting] = useState<boolean>(false)
|
||||
|
||||
// get theme for colors
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
// wrapper to reset state on modal close
|
||||
function wrappedOnDismiss() {
|
||||
setHash(undefined)
|
||||
setAttempting(false)
|
||||
onDismiss()
|
||||
}
|
||||
|
||||
async function onExecute() {
|
||||
setAttempting(true)
|
||||
|
||||
// if callback not returned properly ignore
|
||||
if (!executeCallback) return
|
||||
|
||||
// try delegation and store hash
|
||||
const hash = await executeCallback(proposalId)?.catch((error) => {
|
||||
setAttempting(false)
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
if (hash) {
|
||||
setHash(hash)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onDismiss={wrappedOnDismiss} maxHeight={90}>
|
||||
{!attempting && !hash && (
|
||||
<ContentWrapper gap="lg">
|
||||
<AutoColumn gap="lg" justify="center">
|
||||
<RowBetween>
|
||||
<ThemedText.MediumHeader fontWeight={500}>
|
||||
<Trans>Execute Proposal {proposalId}</Trans>
|
||||
</ThemedText.MediumHeader>
|
||||
<StyledClosed onClick={wrappedOnDismiss} />
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<ThemedText.Body>
|
||||
<Trans>Executing this proposal will enact the calldata on-chain.</Trans>
|
||||
</ThemedText.Body>
|
||||
</RowBetween>
|
||||
<ButtonPrimary onClick={onExecute}>
|
||||
<ThemedText.MediumHeader color="white">
|
||||
<Trans>Execute</Trans>
|
||||
</ThemedText.MediumHeader>
|
||||
</ButtonPrimary>
|
||||
</AutoColumn>
|
||||
</ContentWrapper>
|
||||
)}
|
||||
{attempting && !hash && (
|
||||
<ConfirmOrLoadingWrapper>
|
||||
<RowBetween>
|
||||
<div />
|
||||
<StyledClosed onClick={wrappedOnDismiss} />
|
||||
</RowBetween>
|
||||
<ConfirmedIcon>
|
||||
<CustomLightSpinner src={Circle} alt="loader" size={'90px'} />
|
||||
</ConfirmedIcon>
|
||||
<AutoColumn gap="100px" justify={'center'}>
|
||||
<AutoColumn gap="12px" justify={'center'}>
|
||||
<ThemedText.LargeHeader>
|
||||
<Trans>Executing</Trans>
|
||||
</ThemedText.LargeHeader>
|
||||
</AutoColumn>
|
||||
<ThemedText.SubHeader>
|
||||
<Trans>Confirm this transaction in your wallet</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
</AutoColumn>
|
||||
</ConfirmOrLoadingWrapper>
|
||||
)}
|
||||
{hash && (
|
||||
<ConfirmOrLoadingWrapper>
|
||||
<RowBetween>
|
||||
<div />
|
||||
<StyledClosed onClick={wrappedOnDismiss} />
|
||||
</RowBetween>
|
||||
<ConfirmedIcon>
|
||||
<ArrowUpCircle strokeWidth={0.5} size={90} color={theme.primary1} />
|
||||
</ConfirmedIcon>
|
||||
<AutoColumn gap="100px" justify={'center'}>
|
||||
<AutoColumn gap="12px" justify={'center'}>
|
||||
<ThemedText.LargeHeader>
|
||||
<Trans>Execution Submitted</Trans>
|
||||
</ThemedText.LargeHeader>
|
||||
</AutoColumn>
|
||||
{chainId && (
|
||||
<ExternalLink
|
||||
href={getExplorerLink(chainId, hash, ExplorerDataType.TRANSACTION)}
|
||||
style={{ marginLeft: '4px' }}
|
||||
>
|
||||
<ThemedText.SubHeader>
|
||||
<Trans>View transaction on Explorer</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
</ExternalLink>
|
||||
)}
|
||||
</AutoColumn>
|
||||
</ConfirmOrLoadingWrapper>
|
||||
)}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
153
src/components/vote/QueueModal.tsx
Normal file
153
src/components/vote/QueueModal.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useContext, useState } from 'react'
|
||||
import { ArrowUpCircle, X } from 'react-feather'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
|
||||
import Circle from '../../assets/images/blue-loader.svg'
|
||||
import { useQueueCallback } from '../../state/governance/hooks'
|
||||
import { CustomLightSpinner, ThemedText } from '../../theme'
|
||||
import { ExternalLink } from '../../theme'
|
||||
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
|
||||
import { ButtonPrimary } from '../Button'
|
||||
import { AutoColumn, ColumnCenter } from '../Column'
|
||||
import Modal from '../Modal'
|
||||
import { RowBetween } from '../Row'
|
||||
|
||||
const ContentWrapper = styled(AutoColumn)`
|
||||
width: 100%;
|
||||
padding: 24px;
|
||||
`
|
||||
|
||||
const StyledClosed = styled(X)`
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
|
||||
const ConfirmOrLoadingWrapper = styled.div`
|
||||
width: 100%;
|
||||
padding: 24px;
|
||||
`
|
||||
|
||||
const ConfirmedIcon = styled(ColumnCenter)`
|
||||
padding: 60px 0;
|
||||
`
|
||||
|
||||
interface QueueModalProps {
|
||||
isOpen: boolean
|
||||
onDismiss: () => void
|
||||
proposalId: string | undefined // id for the proposal to queue
|
||||
}
|
||||
|
||||
export default function QueueModal({ isOpen, onDismiss, proposalId }: QueueModalProps) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const queueCallback = useQueueCallback()
|
||||
|
||||
// monitor call to help UI loading state
|
||||
const [hash, setHash] = useState<string | undefined>()
|
||||
const [attempting, setAttempting] = useState<boolean>(false)
|
||||
|
||||
// get theme for colors
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
// wrapper to reset state on modal close
|
||||
function wrappedOnDismiss() {
|
||||
setHash(undefined)
|
||||
setAttempting(false)
|
||||
onDismiss()
|
||||
}
|
||||
|
||||
async function onQueue() {
|
||||
setAttempting(true)
|
||||
|
||||
// if callback not returned properly ignore
|
||||
if (!queueCallback) return
|
||||
|
||||
// try delegation and store hash
|
||||
const hash = await queueCallback(proposalId)?.catch((error) => {
|
||||
setAttempting(false)
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
if (hash) {
|
||||
setHash(hash)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onDismiss={wrappedOnDismiss} maxHeight={90}>
|
||||
{!attempting && !hash && (
|
||||
<ContentWrapper gap="lg">
|
||||
<AutoColumn gap="lg" justify="center">
|
||||
<RowBetween>
|
||||
<ThemedText.MediumHeader fontWeight={500}>
|
||||
<Trans>Queue Proposal {proposalId}</Trans>
|
||||
</ThemedText.MediumHeader>
|
||||
<StyledClosed onClick={wrappedOnDismiss} />
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<ThemedText.Body>
|
||||
<Trans>Adding this proposal to the queue will allow it to be executed, after a delay.</Trans>
|
||||
</ThemedText.Body>
|
||||
</RowBetween>
|
||||
<ButtonPrimary onClick={onQueue}>
|
||||
<ThemedText.MediumHeader color="white">
|
||||
<Trans>Queue</Trans>
|
||||
</ThemedText.MediumHeader>
|
||||
</ButtonPrimary>
|
||||
</AutoColumn>
|
||||
</ContentWrapper>
|
||||
)}
|
||||
{attempting && !hash && (
|
||||
<ConfirmOrLoadingWrapper>
|
||||
<RowBetween>
|
||||
<div />
|
||||
<StyledClosed onClick={wrappedOnDismiss} />
|
||||
</RowBetween>
|
||||
<ConfirmedIcon>
|
||||
<CustomLightSpinner src={Circle} alt="loader" size={'90px'} />
|
||||
</ConfirmedIcon>
|
||||
<AutoColumn gap="100px" justify={'center'}>
|
||||
<AutoColumn gap="12px" justify={'center'}>
|
||||
<ThemedText.LargeHeader>
|
||||
<Trans>Queueing</Trans>
|
||||
</ThemedText.LargeHeader>
|
||||
</AutoColumn>
|
||||
<ThemedText.SubHeader>
|
||||
<Trans>Confirm this transaction in your wallet</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
</AutoColumn>
|
||||
</ConfirmOrLoadingWrapper>
|
||||
)}
|
||||
{hash && (
|
||||
<ConfirmOrLoadingWrapper>
|
||||
<RowBetween>
|
||||
<div />
|
||||
<StyledClosed onClick={wrappedOnDismiss} />
|
||||
</RowBetween>
|
||||
<ConfirmedIcon>
|
||||
<ArrowUpCircle strokeWidth={0.5} size={90} color={theme.primary1} />
|
||||
</ConfirmedIcon>
|
||||
<AutoColumn gap="100px" justify={'center'}>
|
||||
<AutoColumn gap="12px" justify={'center'}>
|
||||
<ThemedText.LargeHeader>
|
||||
<Trans>Transaction Submitted</Trans>
|
||||
</ThemedText.LargeHeader>
|
||||
</AutoColumn>
|
||||
{chainId && (
|
||||
<ExternalLink
|
||||
href={getExplorerLink(chainId, hash, ExplorerDataType.TRANSACTION)}
|
||||
style={{ marginLeft: '4px' }}
|
||||
>
|
||||
<ThemedText.SubHeader>
|
||||
<Trans>View transaction on Explorer</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
</ExternalLink>
|
||||
)}
|
||||
</AutoColumn>
|
||||
</ConfirmOrLoadingWrapper>
|
||||
)}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -45,7 +45,7 @@ interface VoteModalProps {
|
||||
|
||||
export default function VoteModal({ isOpen, onDismiss, proposalId, voteOption }: VoteModalProps) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { voteCallback } = useVoteCallback()
|
||||
const voteCallback = useVoteCallback()
|
||||
const { votes: availableVotes } = useUserVotes()
|
||||
|
||||
// monitor call to help UI loading state
|
||||
@@ -56,7 +56,7 @@ export default function VoteModal({ isOpen, onDismiss, proposalId, voteOption }:
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
// wrapper to reset state on modal close
|
||||
function wrappedOndismiss() {
|
||||
function wrappedOnDismiss() {
|
||||
setHash(undefined)
|
||||
setAttempting(false)
|
||||
onDismiss()
|
||||
@@ -80,7 +80,7 @@ export default function VoteModal({ isOpen, onDismiss, proposalId, voteOption }:
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onDismiss={wrappedOndismiss} maxHeight={90}>
|
||||
<Modal isOpen={isOpen} onDismiss={wrappedOnDismiss} maxHeight={90}>
|
||||
{!attempting && !hash && (
|
||||
<ContentWrapper gap="lg">
|
||||
<AutoColumn gap="lg" justify="center">
|
||||
@@ -94,7 +94,7 @@ export default function VoteModal({ isOpen, onDismiss, proposalId, voteOption }:
|
||||
<Trans>Vote to abstain on proposal {proposalId}</Trans>
|
||||
)}
|
||||
</ThemedText.MediumHeader>
|
||||
<StyledClosed stroke="black" onClick={wrappedOndismiss} />
|
||||
<StyledClosed onClick={wrappedOnDismiss} />
|
||||
</RowBetween>
|
||||
<ThemedText.LargeHeader>
|
||||
<Trans>{formatCurrencyAmount(availableVotes, 4)} Votes</Trans>
|
||||
@@ -117,7 +117,7 @@ export default function VoteModal({ isOpen, onDismiss, proposalId, voteOption }:
|
||||
<ConfirmOrLoadingWrapper>
|
||||
<RowBetween>
|
||||
<div />
|
||||
<StyledClosed onClick={wrappedOndismiss} />
|
||||
<StyledClosed onClick={wrappedOnDismiss} />
|
||||
</RowBetween>
|
||||
<ConfirmedIcon>
|
||||
<CustomLightSpinner src={Circle} alt="loader" size={'90px'} />
|
||||
@@ -138,7 +138,7 @@ export default function VoteModal({ isOpen, onDismiss, proposalId, voteOption }:
|
||||
<ConfirmOrLoadingWrapper>
|
||||
<RowBetween>
|
||||
<div />
|
||||
<StyledClosed onClick={wrappedOndismiss} />
|
||||
<StyledClosed onClick={wrappedOnDismiss} />
|
||||
</RowBetween>
|
||||
<ConfirmedIcon>
|
||||
<ArrowUpCircle strokeWidth={0.5} size={90} color={theme.primary1} />
|
||||
|
||||
@@ -3,7 +3,6 @@ import { SafeAppConnector } from '@gnosis.pm/safe-apps-web3-react'
|
||||
import { ALL_SUPPORTED_CHAIN_IDS, SupportedChainId } from 'constants/chains'
|
||||
import { INFURA_NETWORK_URLS } from 'constants/infura'
|
||||
import { InjectedConnector } from 'web3-react-injected-connector'
|
||||
import { PortisConnector } from 'web3-react-portis-connector'
|
||||
import { WalletConnectConnector } from 'web3-react-walletconnect-connector'
|
||||
import { WalletLinkConnector } from 'web3-react-walletlink-connector'
|
||||
|
||||
@@ -13,7 +12,6 @@ import { FortmaticConnector } from './Fortmatic'
|
||||
import { NetworkConnector } from './NetworkConnector'
|
||||
|
||||
const FORMATIC_KEY = process.env.REACT_APP_FORTMATIC_KEY
|
||||
const PORTIS_ID = process.env.REACT_APP_PORTIS_ID
|
||||
|
||||
export const network = new NetworkConnector({
|
||||
urls: INFURA_NETWORK_URLS,
|
||||
@@ -43,12 +41,6 @@ export const fortmatic = new FortmaticConnector({
|
||||
chainId: 1,
|
||||
})
|
||||
|
||||
// mainnet only
|
||||
export const portis = new PortisConnector({
|
||||
dAppId: PORTIS_ID ?? '',
|
||||
networks: [1],
|
||||
})
|
||||
|
||||
export const walletlink = new WalletLinkConnector({
|
||||
url: INFURA_NETWORK_URLS[SupportedChainId.MAINNET],
|
||||
appName: 'Uniswap',
|
||||
|
||||
@@ -94,7 +94,7 @@ export const CHAIN_INFO: ChainInfoMap = {
|
||||
[SupportedChainId.OPTIMISM]: {
|
||||
networkType: NetworkType.L2,
|
||||
blockWaitMsBeforeWarning: ms`25m`,
|
||||
bridge: 'https://gateway.optimism.io/?chainId=1',
|
||||
bridge: 'https://app.optimism.io/bridge',
|
||||
defaultListUrl: OPTIMISM_LIST,
|
||||
docs: 'https://optimism.io/',
|
||||
explorer: 'https://optimistic.etherscan.io/',
|
||||
@@ -108,7 +108,7 @@ export const CHAIN_INFO: ChainInfoMap = {
|
||||
[SupportedChainId.OPTIMISTIC_KOVAN]: {
|
||||
networkType: NetworkType.L2,
|
||||
blockWaitMsBeforeWarning: ms`25m`,
|
||||
bridge: 'https://gateway.optimism.io/',
|
||||
bridge: 'https://app.optimism.io/bridge',
|
||||
defaultListUrl: OPTIMISM_LIST,
|
||||
docs: 'https://optimism.io/',
|
||||
explorer: 'https://optimistic.etherscan.io/',
|
||||
|
||||
@@ -39,7 +39,21 @@ export const ALL_SUPPORTED_CHAIN_IDS: SupportedChainId[] = Object.values(Support
|
||||
(id) => typeof id === 'number'
|
||||
) as SupportedChainId[]
|
||||
|
||||
export const SUPPORTED_GAS_ESTIMATE_CHAIN_IDS = [SupportedChainId.MAINNET, SupportedChainId.POLYGON]
|
||||
export const SUPPORTED_GAS_ESTIMATE_CHAIN_IDS = [
|
||||
SupportedChainId.MAINNET,
|
||||
SupportedChainId.POLYGON,
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
]
|
||||
|
||||
/**
|
||||
* Unsupported networks for V2 pool behavior.
|
||||
*/
|
||||
export const UNSUPPORTED_V2POOL_CHAIN_IDS = [
|
||||
SupportedChainId.POLYGON,
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
]
|
||||
|
||||
/**
|
||||
* All the chain IDs that are running the Ethereum protocol.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
GOVERNANCE_ALPHA_V0_ADDRESSES,
|
||||
GOVERNANCE_ALPHA_V1_ADDRESSES,
|
||||
GOVERNANCE_BRAVO_ADDRESSES,
|
||||
TIMELOCK_ADDRESS,
|
||||
UNI_ADDRESS,
|
||||
} from './addresses'
|
||||
@@ -11,7 +12,8 @@ export const COMMON_CONTRACT_NAMES: Record<number, { [address: string]: string }
|
||||
[UNI_ADDRESS[SupportedChainId.MAINNET]]: 'UNI',
|
||||
[TIMELOCK_ADDRESS[SupportedChainId.MAINNET]]: 'Timelock',
|
||||
[GOVERNANCE_ALPHA_V0_ADDRESSES[SupportedChainId.MAINNET]]: 'Governance (V0)',
|
||||
[GOVERNANCE_ALPHA_V1_ADDRESSES[SupportedChainId.MAINNET]]: 'Governance',
|
||||
[GOVERNANCE_ALPHA_V1_ADDRESSES[SupportedChainId.MAINNET]]: 'Governance (V1)',
|
||||
[GOVERNANCE_BRAVO_ADDRESSES[SupportedChainId.MAINNET]]: 'Governance',
|
||||
'0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e': 'ENS Registry',
|
||||
'0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41': 'ENS Public Resolver',
|
||||
},
|
||||
|
||||
@@ -35,10 +35,7 @@ export const SUPPORTED_LOCALES = [
|
||||
]
|
||||
export type SupportedLocale = typeof SUPPORTED_LOCALES[number] | 'pseudo'
|
||||
|
||||
// eslint-disable-next-line import/first
|
||||
import * as enUS from 'locales/en-US'
|
||||
export const DEFAULT_LOCALE: SupportedLocale = 'en-US'
|
||||
export const DEFAULT_CATALOG = enUS
|
||||
|
||||
export const LOCALE_LABEL: { [locale in SupportedLocale]: string } = {
|
||||
'af-ZA': 'Afrikaans',
|
||||
|
||||
@@ -2,3 +2,4 @@ export const UNISWAP_GRANTS_START_BLOCK = 11473815
|
||||
export const BRAVO_START_BLOCK = 13059344
|
||||
export const ONE_BIP_START_BLOCK = 13551293
|
||||
export const POLYGON_START_BLOCK = 13786993
|
||||
export const MOONBEAN_START_BLOCK = 14732457
|
||||
|
||||
@@ -44,7 +44,9 @@ type ChainCurrencyList = {
|
||||
}
|
||||
|
||||
const WRAPPED_NATIVE_CURRENCIES_ONLY: ChainTokenList = Object.fromEntries(
|
||||
Object.entries(WRAPPED_NATIVE_CURRENCY).map(([key, value]) => [key, [value]])
|
||||
Object.entries(WRAPPED_NATIVE_CURRENCY)
|
||||
.map(([key, value]) => [key, [value]])
|
||||
.filter(Boolean)
|
||||
)
|
||||
|
||||
// used to construct intermediary pairs for trading
|
||||
@@ -96,7 +98,7 @@ export const ADDITIONAL_BASES: { [chainId: number]: { [tokenAddress: string]: To
|
||||
*/
|
||||
export const CUSTOM_BASES: { [chainId: number]: { [tokenAddress: string]: Token[] } } = {
|
||||
[SupportedChainId.MAINNET]: {
|
||||
[AMPL.address]: [DAI, WRAPPED_NATIVE_CURRENCY[SupportedChainId.MAINNET]],
|
||||
[AMPL.address]: [DAI, WRAPPED_NATIVE_CURRENCY[SupportedChainId.MAINNET] as Token],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -110,29 +112,35 @@ export const COMMON_BASES: ChainCurrencyList = {
|
||||
USDC_MAINNET,
|
||||
USDT,
|
||||
WBTC,
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.MAINNET],
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.MAINNET] as Token,
|
||||
],
|
||||
[SupportedChainId.ROPSTEN]: [
|
||||
nativeOnChain(SupportedChainId.ROPSTEN),
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.ROPSTEN],
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.ROPSTEN] as Token,
|
||||
],
|
||||
[SupportedChainId.RINKEBY]: [
|
||||
nativeOnChain(SupportedChainId.RINKEBY),
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.RINKEBY],
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.RINKEBY] as Token,
|
||||
],
|
||||
[SupportedChainId.GOERLI]: [
|
||||
nativeOnChain(SupportedChainId.GOERLI),
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.GOERLI] as Token,
|
||||
],
|
||||
[SupportedChainId.KOVAN]: [
|
||||
nativeOnChain(SupportedChainId.KOVAN),
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.KOVAN] as Token,
|
||||
],
|
||||
[SupportedChainId.GOERLI]: [nativeOnChain(SupportedChainId.GOERLI), WRAPPED_NATIVE_CURRENCY[SupportedChainId.GOERLI]],
|
||||
[SupportedChainId.KOVAN]: [nativeOnChain(SupportedChainId.KOVAN), WRAPPED_NATIVE_CURRENCY[SupportedChainId.KOVAN]],
|
||||
[SupportedChainId.ARBITRUM_ONE]: [
|
||||
nativeOnChain(SupportedChainId.ARBITRUM_ONE),
|
||||
DAI_ARBITRUM_ONE,
|
||||
USDC_ARBITRUM,
|
||||
USDT_ARBITRUM_ONE,
|
||||
WBTC_ARBITRUM_ONE,
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.ARBITRUM_ONE],
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.ARBITRUM_ONE] as Token,
|
||||
],
|
||||
[SupportedChainId.ARBITRUM_RINKEBY]: [
|
||||
nativeOnChain(SupportedChainId.ARBITRUM_RINKEBY),
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.ARBITRUM_RINKEBY],
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.ARBITRUM_RINKEBY] as Token,
|
||||
],
|
||||
[SupportedChainId.OPTIMISM]: [
|
||||
nativeOnChain(SupportedChainId.OPTIMISM),
|
||||
@@ -152,7 +160,7 @@ export const COMMON_BASES: ChainCurrencyList = {
|
||||
],
|
||||
[SupportedChainId.POLYGON_MUMBAI]: [
|
||||
nativeOnChain(SupportedChainId.POLYGON_MUMBAI),
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.POLYGON_MUMBAI],
|
||||
WRAPPED_NATIVE_CURRENCY[SupportedChainId.POLYGON_MUMBAI] as Token,
|
||||
WETH_POLYGON_MUMBAI,
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1108,6 +1108,13 @@
|
||||
"name": "Ukraine DAO",
|
||||
"symbol": "Ukraine",
|
||||
"decimals": 18
|
||||
},
|
||||
{
|
||||
"chainId": 1,
|
||||
"address": "0x8290D7a64F25e6b5002d98367E8367c1b532b534",
|
||||
"name": "oneUNI",
|
||||
"symbol": "oneUNI",
|
||||
"decimals": 18
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,22 +1,86 @@
|
||||
import { Currency, Ether, NativeCurrency, Token, WETH9 } from '@uniswap/sdk-core'
|
||||
import {
|
||||
USDC_ARBITRUM,
|
||||
USDC_ARBITRUM_RINKEBY,
|
||||
USDC_GÖRLI,
|
||||
USDC_KOVAN,
|
||||
USDC_MAINNET,
|
||||
USDC_OPTIMISM,
|
||||
USDC_OPTIMISTIC_KOVAN,
|
||||
USDC_POLYGON,
|
||||
USDC_POLYGON_MUMBAI,
|
||||
USDC_RINKEBY,
|
||||
USDC_ROPSTEN,
|
||||
} from '@uniswap/smart-order-router'
|
||||
import invariant from 'tiny-invariant'
|
||||
|
||||
import { UNI_ADDRESS } from './addresses'
|
||||
import { SupportedChainId } from './chains'
|
||||
|
||||
export { USDC_ARBITRUM, USDC_MAINNET, USDC_OPTIMISM, USDC_POLYGON }
|
||||
export const USDC_MAINNET = new Token(
|
||||
SupportedChainId.MAINNET,
|
||||
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
|
||||
6,
|
||||
'USDC',
|
||||
'USD//C'
|
||||
)
|
||||
export const USDC_ROPSTEN = new Token(
|
||||
SupportedChainId.ROPSTEN,
|
||||
'0x07865c6e87b9f70255377e024ace6630c1eaa37f',
|
||||
6,
|
||||
'USDC',
|
||||
'USD//C'
|
||||
)
|
||||
export const USDC_RINKEBY = new Token(
|
||||
SupportedChainId.RINKEBY,
|
||||
'0x4DBCdF9B62e891a7cec5A2568C3F4FAF9E8Abe2b',
|
||||
6,
|
||||
'tUSDC',
|
||||
'test USD//C'
|
||||
)
|
||||
export const USDC_GOERLI = new Token(
|
||||
SupportedChainId.GOERLI,
|
||||
'0x07865c6e87b9f70255377e024ace6630c1eaa37f',
|
||||
6,
|
||||
'USDC',
|
||||
'USD//C'
|
||||
)
|
||||
export const USDC_KOVAN = new Token(
|
||||
SupportedChainId.KOVAN,
|
||||
'0x31eeb2d0f9b6fd8642914ab10f4dd473677d80df',
|
||||
6,
|
||||
'USDC',
|
||||
'USD//C'
|
||||
)
|
||||
export const USDC_OPTIMISM = new Token(
|
||||
SupportedChainId.OPTIMISM,
|
||||
'0x7F5c764cBc14f9669B88837ca1490cCa17c31607',
|
||||
6,
|
||||
'USDC',
|
||||
'USD//C'
|
||||
)
|
||||
export const USDC_OPTIMISTIC_KOVAN = new Token(
|
||||
SupportedChainId.OPTIMISTIC_KOVAN,
|
||||
'0x3b8e53b3ab8e01fb57d0c9e893bc4d655aa67d84',
|
||||
6,
|
||||
'USDC',
|
||||
'USD//C'
|
||||
)
|
||||
export const USDC_ARBITRUM = new Token(
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
'0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8',
|
||||
6,
|
||||
'USDC',
|
||||
'USD//C'
|
||||
)
|
||||
export const USDC_ARBITRUM_RINKEBY = new Token(
|
||||
SupportedChainId.ARBITRUM_RINKEBY,
|
||||
'0x09b98f8b2395d076514037ff7d39a091a536206c',
|
||||
6,
|
||||
'USDC',
|
||||
'USD//C'
|
||||
)
|
||||
export const USDC_POLYGON = new Token(
|
||||
SupportedChainId.POLYGON,
|
||||
'0x2791bca1f2de4661ed88a30c99a7a9449aa84174',
|
||||
6,
|
||||
'USDC',
|
||||
'USD//C'
|
||||
)
|
||||
export const USDC_POLYGON_MUMBAI = new Token(
|
||||
SupportedChainId.POLYGON_MUMBAI,
|
||||
'0xe11a86849d99f524cac3e7a0ec1241828e332c62',
|
||||
6,
|
||||
'USDC',
|
||||
'USD//C'
|
||||
)
|
||||
|
||||
export const AMPL = new Token(
|
||||
SupportedChainId.MAINNET,
|
||||
@@ -54,7 +118,7 @@ export const USDC: { [chainId in SupportedChainId]: Token } = {
|
||||
[SupportedChainId.OPTIMISTIC_KOVAN]: USDC_OPTIMISTIC_KOVAN,
|
||||
[SupportedChainId.POLYGON]: USDC_POLYGON,
|
||||
[SupportedChainId.POLYGON_MUMBAI]: USDC_POLYGON_MUMBAI,
|
||||
[SupportedChainId.GOERLI]: USDC_GÖRLI,
|
||||
[SupportedChainId.GOERLI]: USDC_GOERLI,
|
||||
[SupportedChainId.RINKEBY]: USDC_RINKEBY,
|
||||
[SupportedChainId.KOVAN]: USDC_KOVAN,
|
||||
[SupportedChainId.ROPSTEN]: USDC_ROPSTEN,
|
||||
@@ -208,8 +272,8 @@ export const UNI: { [chainId: number]: Token } = {
|
||||
[SupportedChainId.KOVAN]: new Token(SupportedChainId.KOVAN, UNI_ADDRESS[42], 18, 'UNI', 'Uniswap'),
|
||||
}
|
||||
|
||||
export const WRAPPED_NATIVE_CURRENCY: { [chainId: number]: Token } = {
|
||||
...WETH9,
|
||||
export const WRAPPED_NATIVE_CURRENCY: { [chainId: number]: Token | undefined } = {
|
||||
...(WETH9 as Record<SupportedChainId, Token>),
|
||||
[SupportedChainId.OPTIMISM]: new Token(
|
||||
SupportedChainId.OPTIMISM,
|
||||
'0x4200000000000000000000000000000000000006',
|
||||
@@ -265,7 +329,9 @@ class MaticNativeCurrency extends NativeCurrency {
|
||||
|
||||
get wrapped(): Token {
|
||||
if (!isMatic(this.chainId)) throw new Error('Not matic')
|
||||
return WRAPPED_NATIVE_CURRENCY[this.chainId]
|
||||
const wrapped = WRAPPED_NATIVE_CURRENCY[this.chainId]
|
||||
invariant(wrapped instanceof Token)
|
||||
return wrapped
|
||||
}
|
||||
|
||||
public constructor(chainId: number) {
|
||||
@@ -276,7 +342,8 @@ class MaticNativeCurrency extends NativeCurrency {
|
||||
|
||||
export class ExtendedEther extends Ether {
|
||||
public get wrapped(): Token {
|
||||
if (this.chainId in WRAPPED_NATIVE_CURRENCY) return WRAPPED_NATIVE_CURRENCY[this.chainId]
|
||||
const wrapped = WRAPPED_NATIVE_CURRENCY[this.chainId]
|
||||
if (wrapped) return wrapped
|
||||
throw new Error('Unsupported chain ID')
|
||||
}
|
||||
|
||||
@@ -306,7 +373,7 @@ export const TOKEN_SHORTHANDS: { [shorthand: string]: { [chainId in SupportedCha
|
||||
[SupportedChainId.OPTIMISTIC_KOVAN]: USDC_OPTIMISTIC_KOVAN.address,
|
||||
[SupportedChainId.POLYGON]: USDC_POLYGON.address,
|
||||
[SupportedChainId.POLYGON_MUMBAI]: USDC_POLYGON_MUMBAI.address,
|
||||
[SupportedChainId.GOERLI]: USDC_GÖRLI.address,
|
||||
[SupportedChainId.GOERLI]: USDC_GOERLI.address,
|
||||
[SupportedChainId.RINKEBY]: USDC_RINKEBY.address,
|
||||
[SupportedChainId.KOVAN]: USDC_KOVAN.address,
|
||||
[SupportedChainId.ROPSTEN]: USDC_ROPSTEN.address,
|
||||
|
||||
@@ -4,9 +4,8 @@ import INJECTED_ICON_URL from '../assets/images/arrow-right.svg'
|
||||
import COINBASE_ICON_URL from '../assets/images/coinbaseWalletIcon.svg'
|
||||
import FORTMATIC_ICON_URL from '../assets/images/fortmaticIcon.png'
|
||||
import METAMASK_ICON_URL from '../assets/images/metamask.png'
|
||||
import PORTIS_ICON_URL from '../assets/images/portisIcon.png'
|
||||
import WALLETCONNECT_ICON_URL from '../assets/images/walletConnectIcon.svg'
|
||||
import { fortmatic, injected, portis, walletconnect, walletlink } from '../connectors'
|
||||
import { fortmatic, injected, walletconnect, walletlink } from '../connectors'
|
||||
|
||||
interface WalletInfo {
|
||||
connector?: AbstractConnector
|
||||
@@ -73,13 +72,4 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
|
||||
color: '#6748FF',
|
||||
mobile: true,
|
||||
},
|
||||
Portis: {
|
||||
connector: portis,
|
||||
name: 'Portis',
|
||||
iconURL: PORTIS_ICON_URL,
|
||||
description: 'Login using Portis hosted wallet',
|
||||
href: null,
|
||||
color: '#4A6C9B',
|
||||
mobile: true,
|
||||
},
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user