Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c6757ff62 | ||
|
|
0d03b09ae9 | ||
|
|
566da07448 | ||
|
|
31a3840b1f | ||
|
|
f89d7ccd5e | ||
|
|
628417f696 | ||
|
|
ea8c7326d6 | ||
|
|
dd5feaacb2 | ||
|
|
cc919ab3df | ||
|
|
53d6eb0922 | ||
|
|
db0d3cf3fa | ||
|
|
ace4276bcb | ||
|
|
50a2dc9560 | ||
|
|
8cdec6188c | ||
|
|
5325d0cbe5 | ||
|
|
c16e49e774 | ||
|
|
7e709e10db | ||
|
|
7389b178fd | ||
|
|
48f8c6a141 | ||
|
|
091876a374 | ||
|
|
d0e4aa832a | ||
|
|
b17a38d94b | ||
|
|
22136b2708 | ||
|
|
1897330ffc | ||
|
|
6131d0079f | ||
|
|
e6814994f6 | ||
|
|
fea7d3a867 |
3
.env
3
.env
@@ -1,2 +1,3 @@
|
||||
REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847"
|
||||
REACT_APP_LOCALES="locales"
|
||||
REACT_APP_FORTMATIC_KEY="pk_live_357F77728B8EB880"
|
||||
REACT_APP_LOCALES="locales"
|
||||
|
||||
31
.github/workflows/crowdin-sync.yaml
vendored
31
.github/workflows/crowdin-sync.yaml
vendored
@@ -1,6 +1,5 @@
|
||||
name: Crowdin Download
|
||||
|
||||
# hourly we sync translations from Crowdin
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 * * * *' # every hour we download translations and update the pr from crowdin
|
||||
@@ -14,34 +13,26 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v2
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 14
|
||||
registry-url: https://registry.npmjs.org
|
||||
cache: 'yarn'
|
||||
|
||||
- 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'`)
|
||||
- uses: actions/cache@v3
|
||||
id: install-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
path: node_modules/
|
||||
key: ${{ runner.os }}-install-${{ hashFiles('**/yarn.lock') }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
- if: steps.install-cache.outputs.cache-hit != 'true'
|
||||
run: yarn install --frozen-lockfile --ignore-scripts
|
||||
|
||||
- name: Extract translations
|
||||
run: "yarn i18n:extract"
|
||||
- run: yarn i18n:extract
|
||||
|
||||
- name: Synchronize
|
||||
- name: Download Crowdin translations
|
||||
uses: crowdin/github-action@1.4.9
|
||||
with:
|
||||
upload_sources: false
|
||||
|
||||
31
.github/workflows/crowdin.yaml
vendored
31
.github/workflows/crowdin.yaml
vendored
@@ -1,6 +1,5 @@
|
||||
name: Crowdin Upload
|
||||
|
||||
# on any push to main, we upload the translations to be translated
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -12,34 +11,26 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v2
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 14
|
||||
registry-url: https://registry.npmjs.org
|
||||
cache: 'yarn'
|
||||
|
||||
- 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'`)
|
||||
- uses: actions/cache@v3
|
||||
id: install-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
path: node_modules/
|
||||
key: ${{ runner.os }}-install-${{ hashFiles('**/yarn.lock') }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
- if: steps.install-cache.outputs.cache-hit != 'true'
|
||||
run: yarn install --frozen-lockfile --ignore-scripts
|
||||
|
||||
- name: Extract translations
|
||||
run: "yarn i18n:extract"
|
||||
- run: yarn i18n:extract
|
||||
|
||||
- name: Synchronize
|
||||
- name: Upload Crowdin sources
|
||||
uses: crowdin/github-action@1.1.0
|
||||
with:
|
||||
upload_sources: true
|
||||
|
||||
50
.github/workflows/integration-tests.yaml
vendored
50
.github/workflows/integration-tests.yaml
vendored
@@ -1,50 +0,0 @@
|
||||
name: Integration Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
integration-tests:
|
||||
name: Cypress
|
||||
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
|
||||
|
||||
- run: yarn cypress install
|
||||
|
||||
- run: yarn build
|
||||
env:
|
||||
CI: false
|
||||
REACT_APP_SERVICE_WORKER: false
|
||||
|
||||
- run: yarn test:e2e
|
||||
env:
|
||||
CYPRESS_INTEGRATION_TEST_PRIVATE_KEY: ${{ secrets.CYPRESS_INTEGRATION_TEST_PRIVATE_KEY }}
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
25
.github/workflows/lint.yml
vendored
25
.github/workflows/lint.yml
vendored
@@ -14,29 +14,22 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v2
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 14
|
||||
registry-url: https://registry.npmjs.org
|
||||
cache: 'yarn'
|
||||
|
||||
- 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'`)
|
||||
- uses: actions/cache@v3
|
||||
id: install-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
path: node_modules/
|
||||
key: ${{ runner.os }}-install-${{ hashFiles('**/yarn.lock') }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
- if: steps.install-cache.outputs.cache-hit != 'true'
|
||||
run: yarn install --frozen-lockfile --ignore-scripts
|
||||
|
||||
- name: Run eslint w/ autofix
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.owner.login == github.repository_owner }}
|
||||
|
||||
27
.github/workflows/release.yaml
vendored
27
.github/workflows/release.yaml
vendored
@@ -14,8 +14,7 @@ jobs:
|
||||
new_tag: ${{ steps.github_tag_action.outputs.new_tag }}
|
||||
changelog: ${{ steps.github_tag_action.outputs.changelog }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Bump version and push tag
|
||||
id: github_tag_action
|
||||
@@ -31,20 +30,26 @@ jobs:
|
||||
needs: bump_version
|
||||
if: ${{ needs.bump_version.outputs.new_tag != null }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v2
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 14
|
||||
registry-url: https://registry.npmjs.org
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
- uses: actions/cache@v3
|
||||
id: install-cache
|
||||
with:
|
||||
path: node_modules/
|
||||
key: ${{ runner.os }}-install-${{ hashFiles('**/yarn.lock') }}
|
||||
|
||||
- name: Build the IPFS bundle
|
||||
run: yarn build
|
||||
- if: steps.install-cache.outputs.cache-hit != 'true'
|
||||
run: yarn install --frozen-lockfile --ignore-scripts
|
||||
|
||||
- run: yarn prepare
|
||||
|
||||
- run: yarn build
|
||||
|
||||
- name: Pin to IPFS
|
||||
id: upload
|
||||
@@ -68,6 +73,8 @@ jobs:
|
||||
uses: uniswap/convert-cidv0-cidv1@v1.0.0
|
||||
with:
|
||||
cidv0: ${{ steps.upload.outputs.hash }}
|
||||
|
||||
- run: sleep 600
|
||||
|
||||
- name: Update DNS with new IPFS hash
|
||||
env:
|
||||
|
||||
103
.github/workflows/tests-e2e.yaml
vendored
Normal file
103
.github/workflows/tests-e2e.yaml
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
name: End-to-End Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 14
|
||||
registry-url: https://registry.npmjs.org
|
||||
cache: 'yarn'
|
||||
|
||||
- uses: actions/cache@v3
|
||||
id: install-cache
|
||||
with:
|
||||
path: node_modules/
|
||||
key: ${{ runner.os }}-install-${{ hashFiles('**/yarn.lock') }}
|
||||
|
||||
- if: steps.install-cache.outputs.cache-hit != 'true'
|
||||
run: yarn install --frozen-lockfile --ignore-scripts
|
||||
|
||||
- run: yarn prepare
|
||||
|
||||
- run: yarn build
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: build
|
||||
path: build
|
||||
if-no-files-found: error
|
||||
|
||||
- uses: actions/cache@v3
|
||||
id: cypress-cache
|
||||
with:
|
||||
path: /home/runner/.cache/Cypress
|
||||
key: ${{ runner.os }}-cypress-${{ hashFiles('node_modules/cypress') }}
|
||||
|
||||
- if: steps.cypress-cache.outputs.cache-hit != 'true'
|
||||
run: yarn cypress install
|
||||
|
||||
cypress-tests:
|
||||
name: Run tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
containers: [1, 2, 3, 4]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 14
|
||||
registry-url: https://registry.npmjs.org
|
||||
cache: 'yarn'
|
||||
|
||||
- uses: actions/cache@v3
|
||||
id: install-cache
|
||||
with:
|
||||
path: node_modules/ # this should always be a cache hit, from install
|
||||
key: ${{ runner.os }}-install-${{ hashFiles('**/yarn.lock') }}
|
||||
|
||||
- if: steps.install-cache.outputs.cache-hit != 'true'
|
||||
run: yarn install --frozen-lockfile --ignore-scripts
|
||||
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: build
|
||||
path: build
|
||||
|
||||
- uses: actions/cache@v3
|
||||
id: cypress-cache
|
||||
with:
|
||||
path: /home/runner/.cache/Cypress
|
||||
key: ${{ runner.os }}-cypress-${{ hashFiles('node_modules/cypress') }}
|
||||
|
||||
- if: steps.cypress-cache.outputs.cache-hit != 'true'
|
||||
run: yarn cypress install
|
||||
|
||||
- uses: cypress-io/github-action@v4
|
||||
with:
|
||||
install: false
|
||||
start: yarn serve
|
||||
wait-on: 'http://localhost:3000'
|
||||
browser: chrome
|
||||
record: true
|
||||
parallel: true
|
||||
env:
|
||||
CI: false # disables lint checks when building
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
35
.github/workflows/tests-unit.yaml
vendored
Normal file
35
.github/workflows/tests-unit.yaml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: Unit Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
unit-tests:
|
||||
name: Run tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 14
|
||||
registry-url: https://registry.npmjs.org
|
||||
cache: 'yarn'
|
||||
|
||||
- uses: actions/cache@v3
|
||||
id: install-cache
|
||||
with:
|
||||
path: node_modules/
|
||||
key: ${{ runner.os }}-install-${{ hashFiles('**/yarn.lock') }}
|
||||
|
||||
- if: steps.install-cache.outputs.cache-hit != 'true'
|
||||
run: yarn install --frozen-lockfile --ignore-scripts
|
||||
|
||||
- run: yarn prepare
|
||||
|
||||
- run: yarn test
|
||||
40
.github/workflows/unit-tests.yaml
vendored
40
.github/workflows/unit-tests.yaml
vendored
@@ -1,40 +0,0 @@
|
||||
name: Unit Tests
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
unit-tests:
|
||||
name: Unit tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
- name: 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: Run unit tests
|
||||
run: yarn test
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -3,9 +3,6 @@
|
||||
# generated contract types
|
||||
/src/types/v3
|
||||
/src/abis/types
|
||||
/src/lib/locales/**/*.js
|
||||
/src/lib/locales/**/en-US.po
|
||||
/src/lib/locales/**/pseudo.po
|
||||
/src/locales/**/*.js
|
||||
/src/locales/**/en-US.po
|
||||
/src/locales/**/pseudo.po
|
||||
@@ -37,10 +34,8 @@ yarn-error.log*
|
||||
notes.txt
|
||||
.idea/
|
||||
|
||||
.vscode/
|
||||
|
||||
package-lock.json
|
||||
|
||||
cypress/videos
|
||||
cypress/screenshots
|
||||
cypress/fixtures/example.json
|
||||
|
||||
|
||||
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).
|
||||
|
||||
@@ -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',
|
||||
videoUploadOnPasses: 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,5 +1,3 @@
|
||||
import { TEST_ADDRESS_NEVER_USE_SHORTENED } from '../support/commands'
|
||||
|
||||
describe('Landing Page', () => {
|
||||
beforeEach(() => cy.visit('/'))
|
||||
it('loads swap page', () => {
|
||||
@@ -15,9 +13,4 @@ describe('Landing Page', () => {
|
||||
cy.get('#pool-nav-link').click()
|
||||
cy.url().should('include', '/pool')
|
||||
})
|
||||
|
||||
it('is connected', () => {
|
||||
cy.get('#web3-status-connected').click()
|
||||
cy.get('#web3-account-identifier-row').contains(TEST_ADDRESS_NEVER_USE_SHORTENED)
|
||||
})
|
||||
})
|
||||
@@ -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 })
|
||||
})
|
||||
})
|
||||
@@ -48,6 +48,13 @@ describe('Swap', () => {
|
||||
cy.get('#add-recipient-button').should('not.exist')
|
||||
})
|
||||
|
||||
it('ETH to wETH is same value (wrapped swaps have no price impact)', () => {
|
||||
cy.get('#swap-currency-output .open-currency-select-button').click()
|
||||
cy.get('.token-item-0xc778417E063141139Fce010982780140Aa0cD5Ab').click({ force: true })
|
||||
cy.get('#swap-currency-input .token-amount-input').type('0.01', { force: true, delay: 100 })
|
||||
cy.get('#swap-currency-output .token-amount-input').should('have.value', '0.01')
|
||||
})
|
||||
|
||||
describe('expert mode', () => {
|
||||
beforeEach(() => {
|
||||
cy.window().then((win) => {
|
||||
30
cypress/e2e/wallet.test.ts
Normal file
30
cypress/e2e/wallet.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { TEST_ADDRESS_NEVER_USE_SHORTENED } from '../support/ethereum'
|
||||
|
||||
describe('Wallet', () => {
|
||||
before(() => {
|
||||
cy.visit('/')
|
||||
})
|
||||
|
||||
it('displays account details', () => {
|
||||
cy.get('#web3-status-connected').contains(TEST_ADDRESS_NEVER_USE_SHORTENED).click()
|
||||
})
|
||||
|
||||
it('displays account view in wallet modal', () => {
|
||||
cy.get('#web3-account-identifier-row').contains(TEST_ADDRESS_NEVER_USE_SHORTENED)
|
||||
})
|
||||
|
||||
it('changes back to the options grid', () => {
|
||||
cy.get('[data-cy=wallet-change]').click()
|
||||
cy.get('[data-cy=option-grid]').should('exist')
|
||||
})
|
||||
|
||||
it('selects injected wallet option', () => {
|
||||
cy.contains('Injected').click()
|
||||
cy.get('#web3-account-identifier-row').contains(TEST_ADDRESS_NEVER_USE_SHORTENED)
|
||||
})
|
||||
|
||||
it('shows connect buttons after disconnect', () => {
|
||||
cy.get('[data-cy=wallet-disconnect]').click()
|
||||
cy.get('[data-cy=option-grid]').should('exist')
|
||||
})
|
||||
})
|
||||
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"]
|
||||
|
||||
241
package.json
241
package.json
@@ -3,34 +3,61 @@
|
||||
"version": "1.0.7",
|
||||
"description": "Uniswap Interface",
|
||||
"homepage": ".",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"type": "module",
|
||||
"types": "dist/index.d.ts",
|
||||
"main": "dist/cjs/index.cjs",
|
||||
"module": "dist/index.js",
|
||||
"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": {
|
||||
"@coinbase/wallet-sdk": "^3.2.0",
|
||||
"@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",
|
||||
@@ -41,9 +68,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",
|
||||
"@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",
|
||||
@@ -76,19 +105,36 @@
|
||||
"@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.30",
|
||||
"@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.19-beta.0",
|
||||
"@web3-react/walletconnect": "^8.0.26-beta.0",
|
||||
"@uniswap/v3-sdk": "^3.8.2",
|
||||
"@walletconnect/ethereum-provider": "1.7.1",
|
||||
"@web3-react/coinbase-wallet": "^8.0.33-beta.0",
|
||||
"@web3-react/core": "^8.0.33-beta.0",
|
||||
"@web3-react/eip1193": "^8.0.25-beta.0",
|
||||
"@web3-react/empty": "^8.0.19-beta.0",
|
||||
"@web3-react/gnosis-safe": "^8.0.5-beta.0",
|
||||
"@web3-react/metamask": "^8.0.26-beta.0",
|
||||
"@web3-react/network": "^8.0.26-beta.0",
|
||||
"@web3-react/types": "^8.0.19-beta.0",
|
||||
"@web3-react/url": "^8.0.24-beta.0",
|
||||
"@web3-react/walletconnect": "^8.0.34-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",
|
||||
@@ -97,100 +143,14 @@
|
||||
"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",
|
||||
"fortmatic": "^2.4.0",
|
||||
"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-dom": "^17.0.1",
|
||||
"react-ga4": "^1.4.1",
|
||||
"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",
|
||||
"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-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/**/*[!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",
|
||||
"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'"
|
||||
},
|
||||
"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",
|
||||
"@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.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-sdk": "^3.0.1",
|
||||
"@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/types": "^8.0.12-beta.0",
|
||||
"@web3-react/url": "^8.0.17-beta.0",
|
||||
"ajv": "^6.12.3",
|
||||
"cids": "^1.0.0",
|
||||
"ethers": "^5.1.4",
|
||||
"immer": "^9.0.6",
|
||||
"jotai": "^1.3.7",
|
||||
"jsbi": "^3.1.4",
|
||||
"make-plural": "^7.0.0",
|
||||
@@ -199,29 +159,46 @@
|
||||
"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",
|
||||
"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",
|
||||
"web3-react-core": "npm:@web3-react/core@^6.0.9",
|
||||
"wicg-inert": "^3.1.1"
|
||||
"web-vitals": "^2.1.0",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ import {
|
||||
DepositLiquidityStakingTransactionInfo,
|
||||
ExactInputSwapTransactionInfo,
|
||||
ExactOutputSwapTransactionInfo,
|
||||
ExecuteTransactionInfo,
|
||||
MigrateV2LiquidityToV3TransactionInfo,
|
||||
QueueTransactionInfo,
|
||||
RemoveLiquidityV3TransactionInfo,
|
||||
SubmitProposalTransactionInfo,
|
||||
TransactionInfo,
|
||||
@@ -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} />
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useCallback, useContext } from 'react'
|
||||
import { ExternalLink as LinkIcon } from 'react-feather'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
import { updateSelectedWallet } from 'state/user/reducer'
|
||||
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, walletlink } from '../../connectors'
|
||||
import { coinbaseWallet, injected } from '../../connectors'
|
||||
import { SUPPORTED_WALLETS } from '../../constants/wallet'
|
||||
import { clearAllTransactions } from '../../state/transactions/reducer'
|
||||
import { ExternalLink, LinkStyledButton, ThemedText } from '../../theme'
|
||||
@@ -177,7 +177,7 @@ const IconWrapper = styled.div<{ size?: number }>`
|
||||
`};
|
||||
`
|
||||
|
||||
function WrappedStatusIcon({ connector }: { connector: AbstractConnector | Connector }) {
|
||||
function WrappedStatusIcon({ connector }: { connector: Connector }) {
|
||||
return (
|
||||
<IconWrapper size={16}>
|
||||
<StatusIcon connector={connector} />
|
||||
@@ -265,12 +265,16 @@ export default function AccountDetails({
|
||||
<AccountGroupingRow>
|
||||
{formatConnectorName()}
|
||||
<div>
|
||||
{connector !== injected && connector !== walletlink && (
|
||||
{/* Coinbase Wallet reloads the page right now, which breaks the selectedWallet from being set properly on localStorage */}
|
||||
{connector !== coinbaseWallet && (
|
||||
<WalletAction
|
||||
style={{ fontSize: '.825rem', fontWeight: 400, marginRight: '8px' }}
|
||||
onClick={() => {
|
||||
;(connector as any).close()
|
||||
connector.deactivate ? connector.deactivate() : connector.resetState()
|
||||
dispatch(updateSelectedWallet({ wallet: undefined }))
|
||||
openOptions()
|
||||
}}
|
||||
data-cy="wallet-disconnect"
|
||||
>
|
||||
<Trans>Disconnect</Trans>
|
||||
</WalletAction>
|
||||
@@ -280,6 +284,7 @@ export default function AccountDetails({
|
||||
onClick={() => {
|
||||
openOptions()
|
||||
}}
|
||||
data-cy="wallet-change"
|
||||
>
|
||||
<Trans>Change</Trans>
|
||||
</WalletAction>
|
||||
|
||||
@@ -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-ga4'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import store, { AppState } from '../../state'
|
||||
@@ -85,7 +85,10 @@ export default class ErrorBoundary extends React.Component<unknown, ErrorBoundar
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
ReactGA.event('exception', { description: error.toString() + errorInfo.toString(), fatal: true })
|
||||
sendEvent('exception', {
|
||||
description: error.toString() + errorInfo.toString(),
|
||||
fatal: true,
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -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-ga4'
|
||||
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',
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { getWalletForConnector } from 'connectors'
|
||||
import { CHAIN_INFO } from 'constants/chainInfo'
|
||||
import { CHAIN_IDS_TO_NAMES, SupportedChainId } from 'constants/chains'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
@@ -11,12 +12,12 @@ import { ArrowDownCircle, ChevronDown } from 'react-feather'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import { useModalOpen, useToggleModal } from 'state/application/hooks'
|
||||
import { addPopup, ApplicationModal } from 'state/application/reducer'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
import { updateWalletError } from 'state/wallet/reducer'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
|
||||
import { replaceURLParam } from 'utils/routes'
|
||||
|
||||
import { useAppDispatch } from '../../state/hooks'
|
||||
import { switchToNetwork } from '../../utils/switchToNetwork'
|
||||
import { isChainAllowed, switchChain } from 'utils/switchChain'
|
||||
|
||||
const ActiveRowLinkList = styled.div`
|
||||
display: flex;
|
||||
@@ -184,8 +185,8 @@ function Row({
|
||||
targetChain: SupportedChainId
|
||||
onSelectChain: (targetChain: number) => void
|
||||
}) {
|
||||
const { library, chainId } = useActiveWeb3React()
|
||||
if (!library || !chainId) {
|
||||
const { provider, chainId } = useActiveWeb3React()
|
||||
if (!provider || !chainId) {
|
||||
return null
|
||||
}
|
||||
const active = chainId === targetChain
|
||||
@@ -257,8 +258,16 @@ const getChainNameFromId = (id: string | number) => {
|
||||
return CHAIN_IDS_TO_NAMES[id as SupportedChainId] || ''
|
||||
}
|
||||
|
||||
const NETWORK_SELECTOR_CHAINS = [
|
||||
SupportedChainId.MAINNET,
|
||||
SupportedChainId.POLYGON,
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
]
|
||||
|
||||
export default function NetworkSelector() {
|
||||
const { chainId, library } = useActiveWeb3React()
|
||||
const dispatch = useAppDispatch()
|
||||
const { chainId, provider, connector } = useActiveWeb3React()
|
||||
const parsedQs = useParsedQueryString()
|
||||
const { urlChain, urlChainId } = getParsedChainId(parsedQs)
|
||||
const prevChainId = usePrevious(chainId)
|
||||
@@ -271,37 +280,39 @@ export default function NetworkSelector() {
|
||||
|
||||
const info = chainId ? CHAIN_INFO[chainId] : undefined
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
const onSelectChain = useCallback(
|
||||
async (targetChain: number, skipToggle?: boolean) => {
|
||||
if (!connector) return
|
||||
|
||||
const handleChainSwitch = useCallback(
|
||||
(targetChain: number, skipToggle?: boolean) => {
|
||||
if (!library?.provider) return
|
||||
switchToNetwork({ provider: library.provider, chainId: targetChain })
|
||||
.then(() => {
|
||||
if (!skipToggle) {
|
||||
toggle()
|
||||
}
|
||||
history.replace({
|
||||
search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(targetChain)),
|
||||
})
|
||||
const wallet = getWalletForConnector(connector)
|
||||
|
||||
try {
|
||||
dispatch(updateWalletError({ wallet, error: undefined }))
|
||||
await switchChain(connector, targetChain)
|
||||
if (!skipToggle) {
|
||||
toggle()
|
||||
}
|
||||
history.replace({
|
||||
search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(targetChain)),
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to switch networks', error)
|
||||
} catch (error) {
|
||||
console.error('Failed to switch networks', error)
|
||||
|
||||
// we want app network <-> chainId param to be in sync, so if user changes the network by changing the URL
|
||||
// but the request fails, revert the URL back to current chainId
|
||||
if (chainId) {
|
||||
history.replace({ search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(chainId)) })
|
||||
}
|
||||
// we want app network <-> chainId param to be in sync, so if user changes the network by changing the URL
|
||||
// but the request fails, revert the URL back to current chainId
|
||||
if (chainId) {
|
||||
history.replace({ search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(chainId)) })
|
||||
}
|
||||
|
||||
if (!skipToggle) {
|
||||
toggle()
|
||||
}
|
||||
if (!skipToggle) {
|
||||
toggle()
|
||||
}
|
||||
|
||||
dispatch(addPopup({ content: { failedSwitchNetwork: targetChain }, key: `failed-network-switch` }))
|
||||
})
|
||||
dispatch(updateWalletError({ wallet, error: error.message }))
|
||||
dispatch(addPopup({ content: { failedSwitchNetwork: targetChain }, key: `failed-network-switch` }))
|
||||
}
|
||||
},
|
||||
[dispatch, library, toggle, history, chainId]
|
||||
[connector, toggle, dispatch, history, chainId]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -312,9 +323,9 @@ export default function NetworkSelector() {
|
||||
history.replace({ search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(chainId)) })
|
||||
// otherwise assume network change originates from URL
|
||||
} else if (urlChainId && urlChainId !== chainId) {
|
||||
handleChainSwitch(urlChainId, true)
|
||||
onSelectChain(urlChainId, true)
|
||||
}
|
||||
}, [chainId, urlChainId, prevChainId, handleChainSwitch, history])
|
||||
}, [chainId, urlChainId, prevChainId, onSelectChain, history])
|
||||
|
||||
// set chain parameter on initial load if not there
|
||||
useEffect(() => {
|
||||
@@ -323,7 +334,7 @@ export default function NetworkSelector() {
|
||||
}
|
||||
}, [chainId, history, urlChainId, urlChain])
|
||||
|
||||
if (!chainId || !info || !library) {
|
||||
if (!chainId || !info || !provider) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -340,10 +351,11 @@ export default function NetworkSelector() {
|
||||
<FlyoutHeader>
|
||||
<Trans>Select a network</Trans>
|
||||
</FlyoutHeader>
|
||||
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.MAINNET} />
|
||||
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.POLYGON} />
|
||||
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.OPTIMISM} />
|
||||
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.ARBITRUM_ONE} />
|
||||
{NETWORK_SELECTOR_CHAINS.map((chainId: SupportedChainId) =>
|
||||
isChainAllowed(connector, chainId) ? (
|
||||
<Row onSelectChain={onSelectChain} targetChain={chainId} key={chainId} />
|
||||
) : null
|
||||
)}
|
||||
</FlyoutMenuContents>
|
||||
</FlyoutMenu>
|
||||
)}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useUserHasSubmittedClaim } from 'state/transactions/hooks'
|
||||
import { useDarkModeManager } from 'state/user/hooks'
|
||||
import { useNativeCurrencyBalances } from 'state/wallet/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { isChainAllowed } from 'utils/switchChain'
|
||||
|
||||
import { ReactComponent as Logo } from '../../assets/svg/logo.svg'
|
||||
import { ExternalLink, ThemedText } from '../../theme'
|
||||
@@ -76,7 +77,7 @@ const HeaderElement = styled.div`
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
/* addresses safari's lack of support for "gap" */
|
||||
/* addresses safaris lack of support for "gap" */
|
||||
& > *:not(:first-child) {
|
||||
margin-left: 8px;
|
||||
}
|
||||
@@ -246,7 +247,9 @@ const StyledExternalLink = styled(ExternalLink).attrs({
|
||||
`
|
||||
|
||||
export default function Header() {
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
const { account, chainId, connector } = useActiveWeb3React()
|
||||
|
||||
const chainAllowed = chainId && isChainAllowed(connector, chainId)
|
||||
|
||||
const userEthBalance = useNativeCurrencyBalances(account ? [account] : [])?.[account ?? '']
|
||||
const [darkMode] = useDarkModeManager()
|
||||
@@ -265,7 +268,7 @@ export default function Header() {
|
||||
const {
|
||||
infoLink,
|
||||
nativeCurrency: { symbol: nativeCurrencySymbol },
|
||||
} = CHAIN_INFO[chainId ? chainId : SupportedChainId.MAINNET]
|
||||
} = CHAIN_INFO[!chainId || !chainAllowed ? SupportedChainId.MAINNET : chainId]
|
||||
|
||||
return (
|
||||
<HeaderFrame showBackground={scrollY > 45}>
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
import { Connector } from '@web3-react/types'
|
||||
import { AbstractConnector } from 'web3-react-abstract-connector'
|
||||
|
||||
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
|
||||
import FortmaticIcon from '../../assets/images/fortmaticIcon.png'
|
||||
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
|
||||
import { fortmatic, injected, walletconnect, walletlink } from '../../connectors'
|
||||
import { coinbaseWallet, fortmatic, injected, walletConnect } from '../../connectors'
|
||||
import Identicon from '../Identicon'
|
||||
|
||||
export default function StatusIcon({ connector }: { connector: AbstractConnector | Connector }) {
|
||||
export default function StatusIcon({ connector }: { connector: Connector }) {
|
||||
switch (connector) {
|
||||
case injected:
|
||||
return <Identicon />
|
||||
case walletconnect:
|
||||
return <img src={WalletConnectIcon} alt={'WalletConnect'} />
|
||||
case walletlink:
|
||||
return <img src={CoinbaseWalletIcon} alt={'Coinbase Wallet'} />
|
||||
case walletConnect:
|
||||
return <img src={WalletConnectIcon} alt="WalletConnect" />
|
||||
case coinbaseWallet:
|
||||
return <img src={CoinbaseWalletIcon} alt="Coinbase Wallet" />
|
||||
case fortmatic:
|
||||
return <img src={FortmaticIcon} alt={'Fortmatic'} />
|
||||
return <img src={FortmaticIcon} alt="Fortmatic" />
|
||||
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-ga4'
|
||||
import { batch } from 'react-redux'
|
||||
import { Bound } from 'state/mint/v3/actions'
|
||||
import styled from 'styled-components/macro'
|
||||
@@ -158,7 +158,7 @@ export default function LiquidityChartRangeInput({
|
||||
)
|
||||
|
||||
if (isError) {
|
||||
ReactGA.event('exception', { description: error.toString(), 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-ga4'
|
||||
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,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-ga4'
|
||||
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,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-ga4'
|
||||
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',
|
||||
})
|
||||
|
||||
@@ -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-ga4'
|
||||
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',
|
||||
})
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -11,6 +11,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
<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
|
||||
@@ -33,6 +34,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
<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
|
||||
@@ -55,6 +57,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
<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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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-ga4'
|
||||
import AutoSizer from 'react-virtualized-auto-sizer'
|
||||
import { FixedSizeList } from 'react-window'
|
||||
import { Text } from 'rebass'
|
||||
@@ -93,7 +93,7 @@ export function CurrencySearch({
|
||||
|
||||
useEffect(() => {
|
||||
if (isAddressSearch) {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Currency Select',
|
||||
action: 'Search by address',
|
||||
label: isAddressSearch,
|
||||
|
||||
@@ -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-ga4'
|
||||
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-ga4'
|
||||
import { usePopper } from 'react-popper'
|
||||
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
@@ -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,
|
||||
|
||||
@@ -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-ga4'
|
||||
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,4 +1,5 @@
|
||||
import { darken } from 'polished'
|
||||
import { useState } from 'react'
|
||||
import styled, { keyframes } from 'styled-components/macro'
|
||||
|
||||
const Wrapper = styled.button<{ isActive?: boolean; activeElement?: boolean }>`
|
||||
@@ -45,8 +46,10 @@ const ToggleElementHoverStyle = (hasBgColor: boolean, theme: any, isActive?: boo
|
||||
color: isActive ? theme.white : theme.text3,
|
||||
}
|
||||
|
||||
const ToggleElement = styled.span<{ isActive?: boolean; bgColor?: string }>`
|
||||
animation: 0.1s ${({ isActive }) => (isActive ? turnOnToggle : turnOffToggle)} ease-in;
|
||||
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%;
|
||||
@@ -67,9 +70,16 @@ interface ToggleProps {
|
||||
}
|
||||
|
||||
export default function Toggle({ id, bgColor, isActive, toggle }: ToggleProps) {
|
||||
const [isInitialToggleLoad, setIsInitialToggleLoad] = useState(true)
|
||||
|
||||
const switchToggle = () => {
|
||||
toggle()
|
||||
if (isInitialToggleLoad) setIsInitialToggleLoad(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<Wrapper id={id} isActive={isActive} onClick={toggle}>
|
||||
<ToggleElement isActive={isActive} bgColor={bgColor} />
|
||||
<Wrapper id={id} isActive={isActive} onClick={switchToggle}>
|
||||
<ToggleElement isActive={isActive} bgColor={bgColor} isInitialToggleLoad={isInitialToggleLoad} />
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,15 +4,14 @@ import Badge from 'components/Badge'
|
||||
import { CHAIN_INFO } from 'constants/chainInfo'
|
||||
import { L2_CHAIN_IDS, SupportedL2ChainId } from 'constants/chains'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import useAddTokenToMetamask from 'hooks/useAddTokenToMetamask'
|
||||
import { ReactNode, useContext } from 'react'
|
||||
import useCurrencyLogoURIs from 'lib/hooks/useCurrencyLogoURIs'
|
||||
import { ReactNode, useCallback, useContext, useState } from 'react'
|
||||
import { AlertCircle, AlertTriangle, ArrowUpCircle, CheckCircle } from 'react-feather'
|
||||
import { Text } from 'rebass'
|
||||
import { useIsTransactionConfirmed, useTransaction } from 'state/transactions/hooks'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
|
||||
import Circle from '../../assets/images/blue-loader.svg'
|
||||
import MetaMaskLogo from '../../assets/images/metamask.png'
|
||||
import { ExternalLink } from '../../theme'
|
||||
import { CloseIcon, CustomLightSpinner } from '../../theme'
|
||||
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
|
||||
@@ -97,9 +96,25 @@ function TransactionSubmittedContent({
|
||||
}) {
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
const { library } = useActiveWeb3React()
|
||||
const { connector } = useActiveWeb3React()
|
||||
|
||||
const { addToken, success } = useAddTokenToMetamask(currencyToAdd)
|
||||
const token = currencyToAdd?.wrapped
|
||||
const logoURL = useCurrencyLogoURIs(token)[0]
|
||||
|
||||
const [success, setSuccess] = useState<boolean | undefined>()
|
||||
|
||||
const addToken = useCallback(() => {
|
||||
if (!token?.symbol || !connector.watchAsset) return
|
||||
connector
|
||||
.watchAsset({
|
||||
address: token.address,
|
||||
symbol: token.symbol,
|
||||
decimals: token.decimals,
|
||||
image: logoURL,
|
||||
})
|
||||
.then(() => setSuccess(true))
|
||||
.catch(() => setSuccess(false))
|
||||
}, [connector, logoURL, token])
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
@@ -124,13 +139,11 @@ function TransactionSubmittedContent({
|
||||
</Text>
|
||||
</ExternalLink>
|
||||
)}
|
||||
{currencyToAdd && library?.provider?.isMetaMask && (
|
||||
{currencyToAdd && connector.watchAsset && (
|
||||
<ButtonLight mt="12px" padding="6px 12px" width="fit-content" onClick={addToken}>
|
||||
{!success ? (
|
||||
<RowFixed>
|
||||
<Trans>
|
||||
Add {currencyToAdd.symbol} to Metamask <StyledLogo src={MetaMaskLogo} />
|
||||
</Trans>
|
||||
<Trans>Add {currencyToAdd.symbol}</Trans>
|
||||
</RowFixed>
|
||||
) : (
|
||||
<RowFixed>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Connector } from '@web3-react/types'
|
||||
import { ButtonEmpty, ButtonPrimary } from 'components/Button'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import { AbstractConnector } from 'web3-react-abstract-connector'
|
||||
|
||||
import Loader from '../Loader'
|
||||
|
||||
@@ -49,15 +49,13 @@ const LoadingWrapper = styled.div`
|
||||
export default function PendingView({
|
||||
connector,
|
||||
error = false,
|
||||
setPendingError,
|
||||
tryActivation,
|
||||
resetAccountView,
|
||||
openOptions,
|
||||
}: {
|
||||
connector?: AbstractConnector
|
||||
connector: Connector
|
||||
error?: boolean
|
||||
setPendingError: (error: boolean) => void
|
||||
tryActivation: (connector: AbstractConnector) => void
|
||||
resetAccountView: () => void
|
||||
tryActivation: (connector: Connector) => void
|
||||
openOptions: () => void
|
||||
}) {
|
||||
return (
|
||||
<PendingSection>
|
||||
@@ -77,14 +75,13 @@ export default function PendingView({
|
||||
$borderRadius="12px"
|
||||
padding="12px"
|
||||
onClick={() => {
|
||||
setPendingError(false)
|
||||
connector && tryActivation(connector)
|
||||
tryActivation(connector)
|
||||
}}
|
||||
>
|
||||
<Trans>Try Again</Trans>
|
||||
</ButtonPrimary>
|
||||
<ButtonEmpty width="fit-content" padding="0" marginTop={20}>
|
||||
<ThemedText.Link fontSize={12} onClick={resetAccountView}>
|
||||
<ThemedText.Link fontSize={12} onClick={openOptions}>
|
||||
<Trans>Back to wallet selection</Trans>
|
||||
</ThemedText.Link>
|
||||
</ButtonEmpty>
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { Connector } from '@web3-react/types'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { PrivacyPolicy } from 'components/PrivacyPolicy'
|
||||
import Row, { AutoRow } from 'components/Row'
|
||||
import { AutoRow } from 'components/Row'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { ArrowLeft } from 'react-feather'
|
||||
import ReactGA from 'react-ga4'
|
||||
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||
import { updateSelectedWallet } from 'state/user/reducer'
|
||||
import { updateWalletError } from 'state/wallet/reducer'
|
||||
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 } from '../../connectors'
|
||||
import { OVERLAY_READY } from '../../connectors/Fortmatic'
|
||||
import { fortmatic, getWalletForConnector, injected } from '../../connectors'
|
||||
import { SUPPORTED_WALLETS } from '../../constants/wallet'
|
||||
import usePrevious from '../../hooks/usePrevious'
|
||||
import { useModalOpen, useWalletModalToggle } from '../../state/application/hooks'
|
||||
import { ApplicationModal } from '../../state/application/reducer'
|
||||
import { ExternalLink, ThemedText } from '../../theme'
|
||||
@@ -65,24 +64,20 @@ const ContentWrapper = styled.div`
|
||||
padding: 0 1rem 1rem 1rem;
|
||||
border-bottom-left-radius: 20px;
|
||||
border-bottom-right-radius: 20px;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`padding: 0 1rem 1rem 1rem`};
|
||||
`
|
||||
|
||||
const UpperSection = styled.div`
|
||||
position: relative;
|
||||
|
||||
h5 {
|
||||
margin: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
h5:last-child {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-top: 0;
|
||||
font-weight: 500;
|
||||
@@ -111,10 +106,8 @@ const HoverText = styled.div`
|
||||
|
||||
const WALLET_VIEWS = {
|
||||
OPTIONS: 'options',
|
||||
OPTIONS_SECONDARY: 'options_secondary',
|
||||
ACCOUNT: 'account',
|
||||
PENDING: 'pending',
|
||||
LEGAL: 'legal',
|
||||
}
|
||||
|
||||
export default function WalletModal({
|
||||
@@ -126,78 +119,68 @@ export default function WalletModal({
|
||||
confirmedTransactions: string[] // hashes of confirmed
|
||||
ENSName?: string
|
||||
}) {
|
||||
// important that these are destructed from the account-specific web3-react context
|
||||
const { account, connector, activate, error } = useWeb3React()
|
||||
const dispatch = useAppDispatch()
|
||||
const { connector, account } = useWeb3React()
|
||||
|
||||
const [walletView, setWalletView] = useState(WALLET_VIEWS.ACCOUNT)
|
||||
const previousWalletView = usePrevious(walletView)
|
||||
|
||||
const [pendingWallet, setPendingWallet] = useState<AbstractConnector | undefined>()
|
||||
|
||||
const [pendingError, setPendingError] = useState<boolean>()
|
||||
const [pendingConnector, setPendingConnector] = useState<Connector | undefined>()
|
||||
const pendingError = useAppSelector((state) =>
|
||||
pendingConnector ? state.wallet.errorByWallet[getWalletForConnector(pendingConnector)] : undefined
|
||||
)
|
||||
|
||||
const walletModalOpen = useModalOpen(ApplicationModal.WALLET)
|
||||
const toggleWalletModal = useWalletModalToggle()
|
||||
|
||||
const previousAccount = usePrevious(account)
|
||||
const openOptions = useCallback(() => {
|
||||
setWalletView(WALLET_VIEWS.OPTIONS)
|
||||
}, [setWalletView])
|
||||
|
||||
const resetAccountView = useCallback(() => {
|
||||
setPendingError(false)
|
||||
setWalletView(WALLET_VIEWS.ACCOUNT)
|
||||
}, [setPendingError, setWalletView])
|
||||
|
||||
// close on connection, when logged out before
|
||||
useEffect(() => {
|
||||
if (account && !previousAccount && walletModalOpen) {
|
||||
toggleWalletModal()
|
||||
}
|
||||
}, [account, previousAccount, toggleWalletModal, walletModalOpen])
|
||||
|
||||
// always reset to account view
|
||||
useEffect(() => {
|
||||
if (walletModalOpen) {
|
||||
resetAccountView()
|
||||
setWalletView(account ? WALLET_VIEWS.ACCOUNT : WALLET_VIEWS.OPTIONS)
|
||||
}
|
||||
}, [walletModalOpen, resetAccountView])
|
||||
}, [walletModalOpen, setWalletView, account])
|
||||
|
||||
const tryActivation = async (connector: AbstractConnector | undefined) => {
|
||||
let name = ''
|
||||
Object.keys(SUPPORTED_WALLETS).map((key) => {
|
||||
if (connector === SUPPORTED_WALLETS[key].connector) {
|
||||
return (name = SUPPORTED_WALLETS[key].name)
|
||||
}
|
||||
return true
|
||||
})
|
||||
// log selected wallet
|
||||
ReactGA.event({
|
||||
category: 'Wallet',
|
||||
action: 'Change Wallet',
|
||||
label: name,
|
||||
})
|
||||
setPendingWallet(connector) // set wallet for pending view
|
||||
setWalletView(WALLET_VIEWS.PENDING)
|
||||
|
||||
// if the connector is walletconnect and the user has already tried to connect, manually reset the connector
|
||||
if (connector instanceof WalletConnectConnector) {
|
||||
connector.walletConnectProvider = undefined
|
||||
}
|
||||
|
||||
connector &&
|
||||
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
|
||||
useEffect(() => {
|
||||
fortmatic.on(OVERLAY_READY, () => {
|
||||
toggleWalletModal()
|
||||
})
|
||||
}, [toggleWalletModal])
|
||||
if (pendingConnector && walletView !== WALLET_VIEWS.PENDING) {
|
||||
updateWalletError({ wallet: getWalletForConnector(pendingConnector), error: undefined })
|
||||
setPendingConnector(undefined)
|
||||
}
|
||||
}, [pendingConnector, walletView])
|
||||
|
||||
const tryActivation = useCallback(
|
||||
async (connector: Connector) => {
|
||||
const wallet = getWalletForConnector(connector)
|
||||
|
||||
// log selected wallet
|
||||
sendEvent({
|
||||
category: 'Wallet',
|
||||
action: 'Change Wallet',
|
||||
label: wallet,
|
||||
})
|
||||
|
||||
try {
|
||||
// Fortmatic opens it's own modal on activation to log in. This modal has a tabIndex
|
||||
// collision into the WalletModal, so we special case by closing the modal.
|
||||
if (connector === fortmatic) {
|
||||
toggleWalletModal()
|
||||
}
|
||||
|
||||
setPendingConnector(connector)
|
||||
setWalletView(WALLET_VIEWS.PENDING)
|
||||
dispatch(updateWalletError({ wallet, error: undefined }))
|
||||
|
||||
await connector.activate()
|
||||
|
||||
dispatch(updateSelectedWallet({ wallet }))
|
||||
} catch (error) {
|
||||
console.debug(`web3-react connection error: ${error}`)
|
||||
dispatch(updateWalletError({ wallet, error: error.message }))
|
||||
}
|
||||
},
|
||||
[dispatch, toggleWalletModal]
|
||||
)
|
||||
|
||||
// get wallets user can switch too, depending on device/browser
|
||||
function getOptions() {
|
||||
@@ -205,22 +188,30 @@ export default function WalletModal({
|
||||
const isTally = !!window.ethereum?.isTally
|
||||
return Object.keys(SUPPORTED_WALLETS).map((key) => {
|
||||
const option = SUPPORTED_WALLETS[key]
|
||||
const isActive = option.connector === connector
|
||||
|
||||
const optionProps = {
|
||||
active: isActive,
|
||||
id: `connect-${key}`,
|
||||
link: option.href,
|
||||
header: option.name,
|
||||
color: option.color,
|
||||
key,
|
||||
icon: option.iconURL,
|
||||
}
|
||||
|
||||
// check for mobile options
|
||||
if (isMobile) {
|
||||
if (!window.web3 && !window.ethereum && option.mobile) {
|
||||
return (
|
||||
<Option
|
||||
{...optionProps}
|
||||
onClick={() => {
|
||||
option.connector !== connector && !option.href && tryActivation(option.connector)
|
||||
if (!isActive && !option.href && !!option.connector) {
|
||||
tryActivation(option.connector)
|
||||
}
|
||||
}}
|
||||
id={`connect-${key}`}
|
||||
key={key}
|
||||
active={option.connector && option.connector === connector}
|
||||
color={option.color}
|
||||
link={option.href}
|
||||
header={option.name}
|
||||
subheader={null}
|
||||
icon={option.iconURL}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -262,7 +253,7 @@ export default function WalletModal({
|
||||
onClick={() => {
|
||||
option.connector === connector
|
||||
? setWalletView(WALLET_VIEWS.ACCOUNT)
|
||||
: !option.href && tryActivation(option.connector)
|
||||
: !option.href && option.connector && tryActivation(option.connector)
|
||||
}}
|
||||
color={'#E8831D'}
|
||||
header={<Trans>Tally</Trans>}
|
||||
@@ -280,19 +271,13 @@ export default function WalletModal({
|
||||
!isMobile &&
|
||||
!option.mobileOnly && (
|
||||
<Option
|
||||
id={`connect-${key}`}
|
||||
{...optionProps}
|
||||
onClick={() => {
|
||||
option.connector === connector
|
||||
? setWalletView(WALLET_VIEWS.ACCOUNT)
|
||||
: !option.href && tryActivation(option.connector)
|
||||
: !option.href && option.connector && tryActivation(option.connector)
|
||||
}}
|
||||
key={key}
|
||||
active={option.connector === connector}
|
||||
color={option.color}
|
||||
link={option.href}
|
||||
header={option.name}
|
||||
subheader={null} //use option.descriptio to bring back multi-line
|
||||
icon={option.iconURL}
|
||||
/>
|
||||
)
|
||||
)
|
||||
@@ -300,93 +285,56 @@ export default function WalletModal({
|
||||
}
|
||||
|
||||
function getModalContent() {
|
||||
if (error) {
|
||||
return (
|
||||
<UpperSection>
|
||||
<CloseIcon onClick={toggleWalletModal}>
|
||||
<CloseColor />
|
||||
</CloseIcon>
|
||||
<HeaderRow>
|
||||
{error instanceof UnsupportedChainIdError ? <Trans>Wrong Network</Trans> : <Trans>Error connecting</Trans>}
|
||||
</HeaderRow>
|
||||
<ContentWrapper>
|
||||
{error instanceof UnsupportedChainIdError ? (
|
||||
<h5>
|
||||
<Trans>Please connect to a supported network in the dropdown menu or in your wallet.</Trans>
|
||||
</h5>
|
||||
) : (
|
||||
<Trans>Error connecting. Try refreshing the page.</Trans>
|
||||
)}
|
||||
</ContentWrapper>
|
||||
</UpperSection>
|
||||
)
|
||||
}
|
||||
if (walletView === WALLET_VIEWS.LEGAL) {
|
||||
return (
|
||||
<UpperSection>
|
||||
<HeaderRow>
|
||||
<HoverText
|
||||
onClick={() => {
|
||||
setWalletView(
|
||||
(previousWalletView === WALLET_VIEWS.LEGAL ? WALLET_VIEWS.ACCOUNT : previousWalletView) ??
|
||||
WALLET_VIEWS.ACCOUNT
|
||||
)
|
||||
}}
|
||||
>
|
||||
<ArrowLeft />
|
||||
</HoverText>
|
||||
<Row justify="center">
|
||||
<ThemedText.MediumHeader>
|
||||
<Trans>Legal & Privacy</Trans>
|
||||
</ThemedText.MediumHeader>
|
||||
</Row>
|
||||
</HeaderRow>
|
||||
<PrivacyPolicy />
|
||||
</UpperSection>
|
||||
)
|
||||
}
|
||||
if (account && walletView === WALLET_VIEWS.ACCOUNT) {
|
||||
if (walletView === WALLET_VIEWS.ACCOUNT) {
|
||||
return (
|
||||
<AccountDetails
|
||||
toggleWalletModal={toggleWalletModal}
|
||||
pendingTransactions={pendingTransactions}
|
||||
confirmedTransactions={confirmedTransactions}
|
||||
ENSName={ENSName}
|
||||
openOptions={() => setWalletView(WALLET_VIEWS.OPTIONS)}
|
||||
openOptions={openOptions}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
let headerRow
|
||||
if (walletView === WALLET_VIEWS.PENDING) {
|
||||
headerRow = null
|
||||
} else if (walletView === WALLET_VIEWS.ACCOUNT || !!account) {
|
||||
headerRow = (
|
||||
<HeaderRow color="blue">
|
||||
<HoverText onClick={() => setWalletView(account ? WALLET_VIEWS.ACCOUNT : WALLET_VIEWS.OPTIONS)}>
|
||||
<ArrowLeft />
|
||||
</HoverText>
|
||||
</HeaderRow>
|
||||
)
|
||||
} else {
|
||||
headerRow = (
|
||||
<HeaderRow>
|
||||
<HoverText>
|
||||
<Trans>Connect a wallet</Trans>
|
||||
</HoverText>
|
||||
</HeaderRow>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<UpperSection>
|
||||
<CloseIcon onClick={toggleWalletModal}>
|
||||
<CloseColor />
|
||||
</CloseIcon>
|
||||
{walletView !== WALLET_VIEWS.ACCOUNT ? (
|
||||
<HeaderRow color="blue">
|
||||
<HoverText onClick={resetAccountView}>
|
||||
<ArrowLeft />
|
||||
</HoverText>
|
||||
</HeaderRow>
|
||||
) : (
|
||||
<HeaderRow>
|
||||
<HoverText>
|
||||
<Trans>Connect a wallet</Trans>
|
||||
</HoverText>
|
||||
</HeaderRow>
|
||||
)}
|
||||
|
||||
{headerRow}
|
||||
<ContentWrapper>
|
||||
<AutoColumn gap="16px">
|
||||
{walletView === WALLET_VIEWS.PENDING && (
|
||||
{walletView === WALLET_VIEWS.PENDING && pendingConnector && (
|
||||
<PendingView
|
||||
connector={pendingWallet}
|
||||
error={pendingError}
|
||||
setPendingError={setPendingError}
|
||||
openOptions={openOptions}
|
||||
connector={pendingConnector}
|
||||
error={!!pendingError}
|
||||
tryActivation={tryActivation}
|
||||
resetAccountView={resetAccountView}
|
||||
/>
|
||||
)}
|
||||
{walletView !== WALLET_VIEWS.PENDING && <OptionGrid>{getOptions()}</OptionGrid>}
|
||||
{walletView !== WALLET_VIEWS.PENDING && <OptionGrid data-cy="option-grid">{getOptions()}</OptionGrid>}
|
||||
{!pendingError && (
|
||||
<LightCard>
|
||||
<AutoRow style={{ flexWrap: 'nowrap' }}>
|
||||
|
||||
44
src/components/Web3Provider/index.tsx
Normal file
44
src/components/Web3Provider/index.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Web3ReactProvider } from '@web3-react/core'
|
||||
import { Connector } from '@web3-react/types'
|
||||
import { BACKFILLABLE_WALLETS, getConnectorForWallet, gnosisSafe, injected, network, useConnectors } from 'connectors'
|
||||
import { ReactNode, useEffect } from 'react'
|
||||
import { useAppSelector } from 'state/hooks'
|
||||
|
||||
import { isMobile } from '../../utils/userAgent'
|
||||
|
||||
const connect = async (connector: Connector) => {
|
||||
try {
|
||||
if (connector.connectEagerly) {
|
||||
await connector.connectEagerly()
|
||||
} else {
|
||||
await connector.activate()
|
||||
}
|
||||
} catch (error) {
|
||||
console.debug(`web3-react eager connection error: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
export default function Web3Provider({ children }: { children: ReactNode }) {
|
||||
const selectedWalletBackfilled = useAppSelector((state) => state.user.selectedWalletBackfilled)
|
||||
const selectedWallet = useAppSelector((state) => state.user.selectedWallet)
|
||||
|
||||
const connectors = useConnectors(selectedWallet)
|
||||
|
||||
const isMetaMask = !!window.ethereum?.isMetaMask
|
||||
|
||||
useEffect(() => {
|
||||
connect(gnosisSafe)
|
||||
connect(network)
|
||||
|
||||
if (isMobile && isMetaMask) {
|
||||
injected.activate()
|
||||
} else if (selectedWallet) {
|
||||
connect(getConnectorForWallet(selectedWallet))
|
||||
} else if (!selectedWalletBackfilled) {
|
||||
BACKFILLABLE_WALLETS.map(getConnectorForWallet).forEach(connect)
|
||||
}
|
||||
// The dependency list is empty so this is only run once on mount
|
||||
}, []) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return <Web3ReactProvider connectors={connectors}>{children}</Web3ReactProvider>
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useEffect } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { useWeb3React } from 'web3-react-core'
|
||||
|
||||
import { network } from '../../connectors'
|
||||
import { NetworkContextName } from '../../constants/misc'
|
||||
import { useEagerConnect, useInactiveListener } from '../../hooks/web3'
|
||||
|
||||
const MessageWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 20rem;
|
||||
`
|
||||
|
||||
const Message = styled.h2`
|
||||
color: ${({ theme }) => theme.secondary1};
|
||||
`
|
||||
|
||||
export default function Web3ReactManager({ children }: { children: JSX.Element }) {
|
||||
const { active } = useWeb3React()
|
||||
const { active: networkActive, error: networkError, activate: activateNetwork } = useWeb3React(NetworkContextName)
|
||||
|
||||
// try to eagerly connect to an injected provider, if it exists and has granted access already
|
||||
const triedEager = useEagerConnect()
|
||||
|
||||
// after eagerly trying injected, if the network connect ever isn't active or in an error state, activate itd
|
||||
useEffect(() => {
|
||||
if (triedEager && !networkActive && !networkError && !active) {
|
||||
activateNetwork(network)
|
||||
}
|
||||
}, [triedEager, networkActive, networkError, activateNetwork, active])
|
||||
|
||||
// when there's no account connected, react to logins (broadly speaking) on the injected provider, if it exists
|
||||
useInactiveListener(!triedEager)
|
||||
|
||||
// if the account context isn't active, and there's an error on the network context, it's an irrecoverable error
|
||||
if (triedEager && !active && networkError) {
|
||||
return (
|
||||
<MessageWrapper>
|
||||
<Message>
|
||||
<Trans>
|
||||
Oops! An unknown error occurred. Please refresh the page, or visit from another browser or device.
|
||||
</Trans>
|
||||
</Message>
|
||||
</MessageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
return children
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { Connector } from '@web3-react/types'
|
||||
import { getWalletForConnector } from 'connectors'
|
||||
import { darken } from 'polished'
|
||||
import { useMemo } from 'react'
|
||||
import { Activity } from 'react-feather'
|
||||
import { useAppSelector } from 'state/hooks'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
import { AbstractConnector } from 'web3-react-abstract-connector'
|
||||
import { UnsupportedChainIdError, useWeb3React } from 'web3-react-core'
|
||||
import { isChainAllowed } from 'utils/switchChain'
|
||||
|
||||
import { NetworkContextName } from '../../constants/misc'
|
||||
import useENSName from '../../hooks/useENSName'
|
||||
import { useHasSocks } from '../../hooks/useSocksBalance'
|
||||
import { useWalletModalToggle } from '../../state/application/hooks'
|
||||
import { isTransactionRecent, useAllTransactions } from '../../state/transactions/hooks'
|
||||
@@ -131,7 +131,7 @@ function Sock() {
|
||||
)
|
||||
}
|
||||
|
||||
function WrappedStatusIcon({ connector }: { connector: AbstractConnector | Connector }) {
|
||||
function WrappedStatusIcon({ connector }: { connector: Connector }) {
|
||||
return (
|
||||
<IconWrapper size={16}>
|
||||
<StatusIcon connector={connector} />
|
||||
@@ -140,9 +140,11 @@ function WrappedStatusIcon({ connector }: { connector: AbstractConnector | Conne
|
||||
}
|
||||
|
||||
function Web3StatusInner() {
|
||||
const { account, connector, error } = useWeb3React()
|
||||
const { account, connector, chainId, ENSName } = useWeb3React()
|
||||
|
||||
const { ENSName } = useENSName(account ?? undefined)
|
||||
const error = useAppSelector((state) => state.wallet.errorByWallet[getWalletForConnector(connector)])
|
||||
|
||||
const chainAllowed = chainId && isChainAllowed(connector, chainId)
|
||||
|
||||
const allTransactions = useAllTransactions()
|
||||
|
||||
@@ -157,7 +159,27 @@ function Web3StatusInner() {
|
||||
const hasSocks = useHasSocks()
|
||||
const toggleWalletModal = useWalletModalToggle()
|
||||
|
||||
if (account) {
|
||||
if (!chainId) {
|
||||
return null
|
||||
} else if (!chainAllowed) {
|
||||
return (
|
||||
<Web3StatusError onClick={toggleWalletModal}>
|
||||
<NetworkIcon />
|
||||
<Text>
|
||||
<Trans>Wrong Network</Trans>
|
||||
</Text>
|
||||
</Web3StatusError>
|
||||
)
|
||||
} else if (error) {
|
||||
return (
|
||||
<Web3StatusError onClick={toggleWalletModal}>
|
||||
<NetworkIcon />
|
||||
<Text>
|
||||
<Trans>Error</Trans>
|
||||
</Text>
|
||||
</Web3StatusError>
|
||||
)
|
||||
} else if (account) {
|
||||
return (
|
||||
<Web3StatusConnected id="web3-status-connected" onClick={toggleWalletModal} pending={hasPendingTransactions}>
|
||||
{hasPendingTransactions ? (
|
||||
@@ -176,13 +198,6 @@ function Web3StatusInner() {
|
||||
{!hasPendingTransactions && connector && <WrappedStatusIcon connector={connector} />}
|
||||
</Web3StatusConnected>
|
||||
)
|
||||
} else if (error) {
|
||||
return (
|
||||
<Web3StatusError onClick={toggleWalletModal}>
|
||||
<NetworkIcon />
|
||||
<Text>{error instanceof UnsupportedChainIdError ? <Trans>Wrong Network</Trans> : <Trans>Error</Trans>}</Text>
|
||||
</Web3StatusError>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Web3StatusConnect id="connect-wallet" onClick={toggleWalletModal} faded={!account}>
|
||||
@@ -195,10 +210,7 @@ function Web3StatusInner() {
|
||||
}
|
||||
|
||||
export default function Web3Status() {
|
||||
const { active, account } = useWeb3React()
|
||||
const contextNetwork = useWeb3React(NetworkContextName)
|
||||
|
||||
const { ENSName } = useENSName(account ?? undefined)
|
||||
const { ENSName } = useWeb3React()
|
||||
|
||||
const allTransactions = useAllTransactions()
|
||||
|
||||
@@ -213,9 +225,7 @@ export default function Web3Status() {
|
||||
return (
|
||||
<>
|
||||
<Web3StatusInner />
|
||||
{(contextNetwork.active || active) && (
|
||||
<WalletModal ENSName={ENSName ?? undefined} pendingTransactions={pending} confirmedTransactions={confirmed} />
|
||||
)}
|
||||
<WalletModal ENSName={ENSName ?? undefined} pendingTransactions={pending} confirmedTransactions={confirmed} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
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,42 +0,0 @@
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useEffect } from 'react'
|
||||
import ReactGA from 'react-ga4'
|
||||
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._gaCommandSendTiming('Web Vitals', name, Math.round(name === 'CLS' ? delta * 1000 : delta), 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-ga4 -.-
|
||||
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-ga4'
|
||||
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', { gtagOptions: { debug_mode: 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)
|
||||
})
|
||||
}, [])
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ interface StakingModalProps {
|
||||
}
|
||||
|
||||
export default function StakingModal({ isOpen, onDismiss, stakingInfo, userLiquidityUnstaked }: StakingModalProps) {
|
||||
const { library } = useActiveWeb3React()
|
||||
const { provider } = useActiveWeb3React()
|
||||
|
||||
// track and parse user input
|
||||
const [typedValue, setTypedValue] = useState('')
|
||||
@@ -144,7 +144,7 @@ export default function StakingModal({ isOpen, onDismiss, stakingInfo, userLiqui
|
||||
}, [maxAmountInput, onUserInput])
|
||||
|
||||
async function onAttemptToApprove() {
|
||||
if (!pairContract || !library || !deadline) throw new Error('missing dependencies')
|
||||
if (!pairContract || !provider || !deadline) throw new Error('missing dependencies')
|
||||
if (!parsedAmount) throw new Error('missing liquidity amount')
|
||||
|
||||
if (gatherPermitSignature) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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-ga4'
|
||||
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',
|
||||
})
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import { FortmaticConnector as FortmaticConnectorCore } from 'web3-react-fortmatic-connector'
|
||||
|
||||
export const OVERLAY_READY = 'OVERLAY_READY'
|
||||
|
||||
type FormaticSupportedChains = 1 | 3 | 4 | 42
|
||||
|
||||
const CHAIN_ID_NETWORK_ARGUMENT: { readonly [chainId in FormaticSupportedChains]: string | undefined } = {
|
||||
1: undefined,
|
||||
3: 'ropsten',
|
||||
4: 'rinkeby',
|
||||
42: 'kovan',
|
||||
}
|
||||
|
||||
export class FortmaticConnector extends FortmaticConnectorCore {
|
||||
async activate() {
|
||||
if (!this.fortmatic) {
|
||||
const { default: Fortmatic } = await import('fortmatic')
|
||||
|
||||
const { apiKey, chainId } = this as any
|
||||
if (chainId in CHAIN_ID_NETWORK_ARGUMENT) {
|
||||
this.fortmatic = new Fortmatic(apiKey, CHAIN_ID_NETWORK_ARGUMENT[chainId as FormaticSupportedChains])
|
||||
} else {
|
||||
throw new Error(`Unsupported network ID: ${chainId}`)
|
||||
}
|
||||
}
|
||||
|
||||
const provider = this.fortmatic.getProvider()
|
||||
|
||||
const pollForOverlayReady = new Promise<void>((resolve) => {
|
||||
const interval = setInterval(() => {
|
||||
if (provider.overlayReady) {
|
||||
clearInterval(interval)
|
||||
this.emit(OVERLAY_READY)
|
||||
resolve()
|
||||
}
|
||||
}, 200)
|
||||
})
|
||||
|
||||
const [account] = await Promise.all([
|
||||
provider.enable().then((accounts: string[]) => accounts[0]),
|
||||
pollForOverlayReady,
|
||||
])
|
||||
|
||||
return { provider: this.fortmatic.getProvider(), chainId: (this as any).chainId, account }
|
||||
}
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
import invariant from 'tiny-invariant'
|
||||
import { AbstractConnector } from 'web3-react-abstract-connector'
|
||||
import { ConnectorUpdate } from 'web3-react-types'
|
||||
|
||||
interface NetworkConnectorArguments {
|
||||
urls: { [chainId: number]: string }
|
||||
defaultChainId?: number
|
||||
}
|
||||
|
||||
// taken from ethers.js, compatible interface with web3 provider
|
||||
type AsyncSendable = {
|
||||
isMetaMask?: boolean
|
||||
host?: string
|
||||
path?: string
|
||||
sendAsync?: (request: any, callback: (error: any, response: any) => void) => void
|
||||
send?: (request: any, callback: (error: any, response: any) => void) => void
|
||||
}
|
||||
|
||||
class RequestError extends Error {
|
||||
constructor(message: string, public code: number, public data?: unknown) {
|
||||
super(message)
|
||||
}
|
||||
}
|
||||
|
||||
interface BatchItem {
|
||||
request: { jsonrpc: '2.0'; id: number; method: string; params: unknown }
|
||||
resolve: (result: any) => void
|
||||
reject: (error: Error) => void
|
||||
}
|
||||
|
||||
class MiniRpcProvider implements AsyncSendable {
|
||||
public readonly isMetaMask: false = false
|
||||
public readonly chainId: number
|
||||
public readonly url: string
|
||||
public readonly host: string
|
||||
public readonly path: string
|
||||
public readonly batchWaitTimeMs: number
|
||||
|
||||
private readonly connector: NetworkConnector
|
||||
|
||||
private nextId = 1
|
||||
private batchTimeoutId: ReturnType<typeof setTimeout> | null = null
|
||||
private batch: BatchItem[] = []
|
||||
|
||||
constructor(connector: NetworkConnector, chainId: number, url: string, batchWaitTimeMs?: number) {
|
||||
this.connector = connector
|
||||
this.chainId = chainId
|
||||
this.url = url
|
||||
const parsed = new URL(url)
|
||||
this.host = parsed.host
|
||||
this.path = parsed.pathname
|
||||
// how long to wait to batch calls
|
||||
this.batchWaitTimeMs = batchWaitTimeMs ?? 50
|
||||
}
|
||||
|
||||
public readonly clearBatch = async () => {
|
||||
console.debug('Clearing batch', this.batch)
|
||||
let batch = this.batch
|
||||
|
||||
batch = batch.filter((b) => {
|
||||
if (b.request.method === 'wallet_switchEthereumChain') {
|
||||
try {
|
||||
this.connector.changeChainId(parseInt((b.request.params as [{ chainId: string }])[0].chainId))
|
||||
b.resolve({ id: b.request.id })
|
||||
} catch (error) {
|
||||
b.reject(error)
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
this.batch = []
|
||||
this.batchTimeoutId = null
|
||||
let response: Response
|
||||
try {
|
||||
response = await fetch(this.url, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
||||
body: JSON.stringify(batch.map((item) => item.request)),
|
||||
})
|
||||
} catch (error) {
|
||||
batch.forEach(({ reject }) => reject(new Error('Failed to send batch call')))
|
||||
return
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
batch.forEach(({ reject }) => reject(new RequestError(`${response.status}: ${response.statusText}`, -32000)))
|
||||
return
|
||||
}
|
||||
|
||||
let json
|
||||
try {
|
||||
json = await response.json()
|
||||
} catch (error) {
|
||||
batch.forEach(({ reject }) => reject(new Error('Failed to parse JSON response')))
|
||||
return
|
||||
}
|
||||
const byKey = batch.reduce<{ [id: number]: BatchItem }>((memo, current) => {
|
||||
memo[current.request.id] = current
|
||||
return memo
|
||||
}, {})
|
||||
for (const result of json) {
|
||||
const {
|
||||
resolve,
|
||||
reject,
|
||||
request: { method },
|
||||
} = byKey[result.id]
|
||||
if ('error' in result) {
|
||||
reject(new RequestError(result?.error?.message, result?.error?.code, result?.error?.data))
|
||||
} else if ('result' in result && resolve) {
|
||||
resolve(result.result)
|
||||
} else {
|
||||
reject(new RequestError(`Received unexpected JSON-RPC response to ${method} request.`, -32000, result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly sendAsync = (
|
||||
request: {
|
||||
jsonrpc: '2.0'
|
||||
id: number | string | null
|
||||
method: string
|
||||
params?: unknown[] | Record<string, unknown>
|
||||
},
|
||||
callback: (error: any, response: any) => void
|
||||
): void => {
|
||||
this.request(request.method, request.params)
|
||||
.then((result) => callback(null, { jsonrpc: '2.0', id: request.id, result }))
|
||||
.catch((error) => callback(error, null))
|
||||
}
|
||||
|
||||
public readonly request = async (
|
||||
method: string | { method: string; params: unknown[] },
|
||||
params?: unknown[] | Record<string, unknown>
|
||||
): Promise<unknown> => {
|
||||
if (typeof method !== 'string') {
|
||||
return this.request(method.method, method.params)
|
||||
}
|
||||
if (method === 'eth_chainId') {
|
||||
return `0x${this.chainId.toString(16)}`
|
||||
}
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
this.batch.push({
|
||||
request: {
|
||||
jsonrpc: '2.0',
|
||||
id: this.nextId++,
|
||||
method,
|
||||
params,
|
||||
},
|
||||
resolve,
|
||||
reject,
|
||||
})
|
||||
})
|
||||
this.batchTimeoutId = this.batchTimeoutId ?? setTimeout(this.clearBatch, this.batchWaitTimeMs)
|
||||
return promise
|
||||
}
|
||||
}
|
||||
|
||||
export class NetworkConnector extends AbstractConnector {
|
||||
private readonly providers: { [chainId: number]: MiniRpcProvider }
|
||||
private currentChainId: number
|
||||
|
||||
constructor({ urls, defaultChainId }: NetworkConnectorArguments) {
|
||||
invariant(defaultChainId || Object.keys(urls).length === 1, 'defaultChainId is a required argument with >1 url')
|
||||
super({ supportedChainIds: Object.keys(urls).map((k): number => Number(k)) })
|
||||
|
||||
this.currentChainId = defaultChainId ?? Number(Object.keys(urls)[0])
|
||||
this.providers = Object.keys(urls).reduce<{ [chainId: number]: MiniRpcProvider }>((accumulator, chainId) => {
|
||||
accumulator[Number(chainId)] = new MiniRpcProvider(this, Number(chainId), urls[Number(chainId)])
|
||||
return accumulator
|
||||
}, {})
|
||||
}
|
||||
|
||||
public get provider(): MiniRpcProvider {
|
||||
return this.providers[this.currentChainId]
|
||||
}
|
||||
|
||||
public async activate(): Promise<ConnectorUpdate> {
|
||||
return { provider: this.providers[this.currentChainId], chainId: this.currentChainId, account: null }
|
||||
}
|
||||
|
||||
public async getProvider(): Promise<MiniRpcProvider> {
|
||||
return this.providers[this.currentChainId]
|
||||
}
|
||||
|
||||
public async getChainId(): Promise<number> {
|
||||
return this.currentChainId
|
||||
}
|
||||
|
||||
public async getAccount(): Promise<null> {
|
||||
return null
|
||||
}
|
||||
|
||||
public deactivate() {
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* Meant to be called only by MiniRpcProvider
|
||||
* @param chainId the new chain id
|
||||
*/
|
||||
public changeChainId(chainId: number) {
|
||||
if (chainId in this.providers) {
|
||||
this.currentChainId = chainId
|
||||
this.emitUpdate({
|
||||
chainId,
|
||||
account: null,
|
||||
provider: this.providers[chainId],
|
||||
})
|
||||
} else {
|
||||
throw new Error(`Unsupported chain ID: ${chainId}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/connectors/fortmatic.d.ts
vendored
1
src/connectors/fortmatic.d.ts
vendored
@@ -1 +0,0 @@
|
||||
declare module 'formatic'
|
||||
@@ -1,49 +1,150 @@
|
||||
import { Web3Provider } from '@ethersproject/providers'
|
||||
import { SafeAppConnector } from '@gnosis.pm/safe-apps-web3-react'
|
||||
import { ALL_SUPPORTED_CHAIN_IDS, SupportedChainId } from 'constants/chains'
|
||||
import { CoinbaseWallet } from '@web3-react/coinbase-wallet'
|
||||
import { initializeConnector, Web3ReactHooks } from '@web3-react/core'
|
||||
import { EIP1193 } from '@web3-react/eip1193'
|
||||
import { GnosisSafe } from '@web3-react/gnosis-safe'
|
||||
import { MetaMask } from '@web3-react/metamask'
|
||||
import { Network } from '@web3-react/network'
|
||||
import { Connector } from '@web3-react/types'
|
||||
import { WalletConnect } from '@web3-react/walletconnect'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { INFURA_NETWORK_URLS } from 'constants/infura'
|
||||
import { InjectedConnector } from 'web3-react-injected-connector'
|
||||
import { WalletConnectConnector } from 'web3-react-walletconnect-connector'
|
||||
import { WalletLinkConnector } from 'web3-react-walletlink-connector'
|
||||
import Fortmatic from 'fortmatic'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import UNISWAP_LOGO_URL from '../assets/svg/logo.svg'
|
||||
import getLibrary from '../utils/getLibrary'
|
||||
import { FortmaticConnector } from './Fortmatic'
|
||||
import { NetworkConnector } from './NetworkConnector'
|
||||
|
||||
const FORMATIC_KEY = process.env.REACT_APP_FORTMATIC_KEY
|
||||
|
||||
export const network = new NetworkConnector({
|
||||
urls: INFURA_NETWORK_URLS,
|
||||
defaultChainId: 1,
|
||||
})
|
||||
|
||||
let networkLibrary: Web3Provider | undefined
|
||||
export function getNetworkLibrary(): Web3Provider {
|
||||
return (networkLibrary = networkLibrary ?? getLibrary(network.provider))
|
||||
export enum Wallet {
|
||||
INJECTED = 'INJECTED',
|
||||
COINBASE_WALLET = 'COINBASE_WALLET',
|
||||
WALLET_CONNECT = 'WALLET_CONNECT',
|
||||
FORTMATIC = 'FORTMATIC',
|
||||
NETWORK = 'NETWORK',
|
||||
GNOSIS_SAFE = 'GNOSIS_SAFE',
|
||||
}
|
||||
|
||||
export const injected = new InjectedConnector({
|
||||
supportedChainIds: ALL_SUPPORTED_CHAIN_IDS,
|
||||
})
|
||||
export const BACKFILLABLE_WALLETS = [Wallet.COINBASE_WALLET, Wallet.WALLET_CONNECT, Wallet.INJECTED]
|
||||
export const SELECTABLE_WALLETS = [...BACKFILLABLE_WALLETS, Wallet.FORTMATIC]
|
||||
|
||||
export const gnosisSafe = new SafeAppConnector()
|
||||
function onError(error: Error) {
|
||||
console.debug(`web3-react error: ${error}`)
|
||||
}
|
||||
|
||||
export const walletconnect = new WalletConnectConnector({
|
||||
supportedChainIds: ALL_SUPPORTED_CHAIN_IDS,
|
||||
rpc: INFURA_NETWORK_URLS,
|
||||
qrcode: true,
|
||||
})
|
||||
export function getWalletForConnector(connector: Connector) {
|
||||
switch (connector) {
|
||||
case injected:
|
||||
return Wallet.INJECTED
|
||||
case coinbaseWallet:
|
||||
return Wallet.COINBASE_WALLET
|
||||
case walletConnect:
|
||||
return Wallet.WALLET_CONNECT
|
||||
case fortmatic:
|
||||
return Wallet.FORTMATIC
|
||||
case network:
|
||||
return Wallet.NETWORK
|
||||
case gnosisSafe:
|
||||
return Wallet.GNOSIS_SAFE
|
||||
default:
|
||||
throw Error('unsupported connector')
|
||||
}
|
||||
}
|
||||
|
||||
// mainnet only
|
||||
export const fortmatic = new FortmaticConnector({
|
||||
apiKey: FORMATIC_KEY ?? '',
|
||||
chainId: 1,
|
||||
})
|
||||
export function getConnectorForWallet(wallet: Wallet) {
|
||||
switch (wallet) {
|
||||
case Wallet.INJECTED:
|
||||
return injected
|
||||
case Wallet.COINBASE_WALLET:
|
||||
return coinbaseWallet
|
||||
case Wallet.WALLET_CONNECT:
|
||||
return walletConnect
|
||||
case Wallet.FORTMATIC:
|
||||
return fortmatic
|
||||
case Wallet.NETWORK:
|
||||
return network
|
||||
case Wallet.GNOSIS_SAFE:
|
||||
return gnosisSafe
|
||||
}
|
||||
}
|
||||
|
||||
export const walletlink = new WalletLinkConnector({
|
||||
url: INFURA_NETWORK_URLS[SupportedChainId.MAINNET],
|
||||
appName: 'Uniswap',
|
||||
appLogoUrl: UNISWAP_LOGO_URL,
|
||||
supportedChainIds: ALL_SUPPORTED_CHAIN_IDS,
|
||||
})
|
||||
function getHooksForWallet(wallet: Wallet) {
|
||||
switch (wallet) {
|
||||
case Wallet.INJECTED:
|
||||
return injectedHooks
|
||||
case Wallet.COINBASE_WALLET:
|
||||
return coinbaseWalletHooks
|
||||
case Wallet.WALLET_CONNECT:
|
||||
return walletConnectHooks
|
||||
case Wallet.FORTMATIC:
|
||||
return fortmaticHooks
|
||||
case Wallet.NETWORK:
|
||||
return networkHooks
|
||||
case Wallet.GNOSIS_SAFE:
|
||||
return gnosisSafeHooks
|
||||
}
|
||||
}
|
||||
|
||||
export const [network, networkHooks] = initializeConnector<Network>(
|
||||
(actions) => new Network({ actions, urlMap: INFURA_NETWORK_URLS, defaultChainId: 1 })
|
||||
)
|
||||
|
||||
export const [injected, injectedHooks] = initializeConnector<MetaMask>((actions) => new MetaMask({ actions, onError }))
|
||||
|
||||
export const [gnosisSafe, gnosisSafeHooks] = initializeConnector<GnosisSafe>((actions) => new GnosisSafe({ actions }))
|
||||
|
||||
export const [walletConnect, walletConnectHooks] = initializeConnector<WalletConnect>(
|
||||
(actions) =>
|
||||
new WalletConnect({
|
||||
actions,
|
||||
options: {
|
||||
rpc: INFURA_NETWORK_URLS,
|
||||
qrcode: true,
|
||||
},
|
||||
onError,
|
||||
})
|
||||
)
|
||||
|
||||
export const [fortmatic, fortmaticHooks] = initializeConnector<EIP1193>(
|
||||
(actions) => new EIP1193({ actions, provider: new Fortmatic(process.env.REACT_APP_FORTMATIC_KEY).getProvider() })
|
||||
)
|
||||
|
||||
export const [coinbaseWallet, coinbaseWalletHooks] = initializeConnector<CoinbaseWallet>(
|
||||
(actions) =>
|
||||
new CoinbaseWallet({
|
||||
actions,
|
||||
options: {
|
||||
url: INFURA_NETWORK_URLS[SupportedChainId.MAINNET],
|
||||
appName: 'Uniswap',
|
||||
appLogoUrl: UNISWAP_LOGO_URL,
|
||||
},
|
||||
onError,
|
||||
})
|
||||
)
|
||||
|
||||
interface ConnectorListItem {
|
||||
connector: Connector
|
||||
hooks: Web3ReactHooks
|
||||
}
|
||||
|
||||
function getConnectorListItemForWallet(wallet: Wallet) {
|
||||
return {
|
||||
connector: getConnectorForWallet(wallet),
|
||||
hooks: getHooksForWallet(wallet),
|
||||
}
|
||||
}
|
||||
|
||||
export function useConnectors(selectedWallet: Wallet | undefined) {
|
||||
return useMemo(() => {
|
||||
const connectors: ConnectorListItem[] = [{ connector: gnosisSafe, hooks: gnosisSafeHooks }]
|
||||
if (selectedWallet) {
|
||||
connectors.push(getConnectorListItemForWallet(selectedWallet))
|
||||
}
|
||||
connectors.push(
|
||||
...SELECTABLE_WALLETS.filter((wallet) => wallet !== selectedWallet).map(getConnectorListItemForWallet)
|
||||
)
|
||||
connectors.push({ connector: network, hooks: networkHooks })
|
||||
const web3ReactConnectors: [Connector, Web3ReactHooks][] = connectors.map(({ connector, hooks }) => [
|
||||
connector,
|
||||
hooks,
|
||||
])
|
||||
return web3ReactConnectors
|
||||
}, [selectedWallet])
|
||||
}
|
||||
|
||||
@@ -46,6 +46,15 @@ export const SUPPORTED_GAS_ESTIMATE_CHAIN_IDS = [
|
||||
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,3 +1,5 @@
|
||||
import { JsonRpcProvider } from '@ethersproject/providers'
|
||||
|
||||
import { SupportedChainId } from './chains'
|
||||
|
||||
const INFURA_KEY = process.env.REACT_APP_INFURA_KEY
|
||||
@@ -5,6 +7,8 @@ if (typeof INFURA_KEY === 'undefined') {
|
||||
throw new Error(`REACT_APP_INFURA_KEY must be a defined environment variable`)
|
||||
}
|
||||
|
||||
export const MAINNET_PROVIDER = new JsonRpcProvider(`https://mainnet.infura.io/v3/${INFURA_KEY}`)
|
||||
|
||||
/**
|
||||
* These are the network URLs used by the interface when there is not another available source of chain data
|
||||
*/
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { AbstractConnector } from 'web3-react-abstract-connector'
|
||||
import { Connector } from '@web3-react/types'
|
||||
|
||||
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 WALLETCONNECT_ICON_URL from '../assets/images/walletConnectIcon.svg'
|
||||
import { fortmatic, injected, walletconnect, walletlink } from '../connectors'
|
||||
import { coinbaseWallet, fortmatic, injected, Wallet, walletConnect } from '../connectors'
|
||||
|
||||
interface WalletInfo {
|
||||
connector?: AbstractConnector
|
||||
connector?: Connector
|
||||
wallet?: Wallet
|
||||
name: string
|
||||
iconURL: string
|
||||
description: string
|
||||
@@ -22,6 +23,7 @@ interface WalletInfo {
|
||||
export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
|
||||
INJECTED: {
|
||||
connector: injected,
|
||||
wallet: Wallet.INJECTED,
|
||||
name: 'Injected',
|
||||
iconURL: INJECTED_ICON_URL,
|
||||
description: 'Injected web3 provider.',
|
||||
@@ -31,6 +33,7 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
|
||||
},
|
||||
METAMASK: {
|
||||
connector: injected,
|
||||
wallet: Wallet.INJECTED,
|
||||
name: 'MetaMask',
|
||||
iconURL: METAMASK_ICON_URL,
|
||||
description: 'Easy-to-use browser extension.',
|
||||
@@ -38,7 +41,8 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
|
||||
color: '#E8831D',
|
||||
},
|
||||
WALLET_CONNECT: {
|
||||
connector: walletconnect,
|
||||
connector: walletConnect,
|
||||
wallet: Wallet.WALLET_CONNECT,
|
||||
name: 'WalletConnect',
|
||||
iconURL: WALLETCONNECT_ICON_URL,
|
||||
description: 'Connect to Trust Wallet, Rainbow Wallet and more...',
|
||||
@@ -46,8 +50,9 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
|
||||
color: '#4196FC',
|
||||
mobile: true,
|
||||
},
|
||||
WALLET_LINK: {
|
||||
connector: walletlink,
|
||||
COINBASE_WALLET: {
|
||||
connector: coinbaseWallet,
|
||||
wallet: Wallet.COINBASE_WALLET,
|
||||
name: 'Coinbase Wallet',
|
||||
iconURL: COINBASE_ICON_URL,
|
||||
description: 'Use Coinbase Wallet app on mobile device',
|
||||
@@ -65,6 +70,7 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
|
||||
},
|
||||
FORTMATIC: {
|
||||
connector: fortmatic,
|
||||
wallet: Wallet.FORTMATIC,
|
||||
name: 'Fortmatic',
|
||||
iconURL: FORTMATIC_ICON_URL,
|
||||
description: 'Login using Fortmatic hosted wallet',
|
||||
|
||||
@@ -120,11 +120,15 @@ export function useSearchInactiveTokenLists(search: string | undefined, minResul
|
||||
if (!list) continue
|
||||
for (const tokenInfo of list.tokens) {
|
||||
if (tokenInfo.chainId === chainId && tokenFilter(tokenInfo)) {
|
||||
const wrapped: WrappedTokenInfo = new WrappedTokenInfo(tokenInfo, list)
|
||||
if (!(wrapped.address in activeTokens) && !addressSet[wrapped.address]) {
|
||||
addressSet[wrapped.address] = true
|
||||
result.push(wrapped)
|
||||
if (result.length >= minResults) return result
|
||||
try {
|
||||
const wrapped: WrappedTokenInfo = new WrappedTokenInfo(tokenInfo, list)
|
||||
if (!(wrapped.address in activeTokens) && !addressSet[wrapped.address]) {
|
||||
addressSet[wrapped.address] = true
|
||||
result.push(wrapped)
|
||||
if (result.length >= minResults) return result
|
||||
}
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import ms from 'ms.macro'
|
||||
import { useEffect } from 'react'
|
||||
import ReactGA from 'react-ga4'
|
||||
import { ApplicationModal, setOpenModal } from 'state/application/reducer'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
|
||||
@@ -8,26 +9,34 @@ export default function useAccountRiskCheck(account: string | null | undefined)
|
||||
|
||||
useEffect(() => {
|
||||
if (account) {
|
||||
const headers = new Headers({ 'Content-Type': 'application/json' })
|
||||
fetch('https://screening-worker.uniswap.workers.dev', {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({ address: account }),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
if (data.block) {
|
||||
dispatch(setOpenModal(ApplicationModal.BLOCKED_ACCOUNT))
|
||||
ReactGA.event({
|
||||
category: 'Address Screening',
|
||||
action: 'blocked',
|
||||
label: account,
|
||||
const riskCheckLocalStorageKey = `risk-check-${account}`
|
||||
const now = Date.now()
|
||||
try {
|
||||
const storedTime = localStorage.getItem(riskCheckLocalStorageKey)
|
||||
const checkExpirationTime = storedTime ? parseInt(storedTime) : now - 1
|
||||
if (checkExpirationTime < Date.now()) {
|
||||
const headers = new Headers({ 'Content-Type': 'application/json' })
|
||||
fetch('https://screening-worker.uniswap.workers.dev', {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({ address: account }),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
if (data.block) {
|
||||
dispatch(setOpenModal(ApplicationModal.BLOCKED_ACCOUNT))
|
||||
sendEvent({
|
||||
category: 'Address Screening',
|
||||
action: 'blocked',
|
||||
label: account,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch(setOpenModal(null))
|
||||
})
|
||||
.catch(() => dispatch(setOpenModal(null)))
|
||||
}
|
||||
} finally {
|
||||
localStorage.setItem(riskCheckLocalStorageKey, (now + ms`7 days`).toString())
|
||||
}
|
||||
}
|
||||
}, [account, dispatch])
|
||||
}
|
||||
|
||||
@@ -1,18 +1,6 @@
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import { Web3Provider } from '@ethersproject/providers'
|
||||
import { useWeb3React } from 'web3-react-core'
|
||||
|
||||
import { NetworkContextName } from '../constants/misc'
|
||||
// TODO(vm): Rm this file once #3759 is merged.
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
|
||||
export default function useActiveWeb3React() {
|
||||
const interfaceContext = useWeb3React<Web3Provider>()
|
||||
const interfaceNetworkContext = useWeb3React<Web3Provider>(
|
||||
process.env.REACT_APP_IS_WIDGET ? undefined : NetworkContextName
|
||||
)
|
||||
|
||||
if (interfaceContext.active) {
|
||||
return interfaceContext
|
||||
}
|
||||
|
||||
return interfaceNetworkContext
|
||||
return useWeb3React()
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { Currency, Token } from '@uniswap/sdk-core'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import useCurrencyLogoURIs from 'lib/hooks/useCurrencyLogoURIs'
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
export default function useAddTokenToMetamask(currencyToAdd: Currency | undefined): {
|
||||
addToken: () => void
|
||||
success: boolean | undefined
|
||||
} {
|
||||
const { library } = useActiveWeb3React()
|
||||
|
||||
const token: Token | undefined = currencyToAdd?.wrapped
|
||||
|
||||
const [success, setSuccess] = useState<boolean | undefined>()
|
||||
const logoURL = useCurrencyLogoURIs(token)[0]
|
||||
|
||||
const addToken = useCallback(() => {
|
||||
if (library && library?.provider?.isMetaMask && library.provider.request && token) {
|
||||
library.provider
|
||||
.request({
|
||||
method: 'wallet_watchAsset',
|
||||
params: {
|
||||
//@ts-ignore // need this for incorrect ethers provider type
|
||||
type: 'ERC20',
|
||||
options: {
|
||||
address: token.address,
|
||||
symbol: token.symbol,
|
||||
decimals: token.decimals,
|
||||
image: logoURL,
|
||||
},
|
||||
},
|
||||
})
|
||||
.then((success) => {
|
||||
setSuccess(success)
|
||||
})
|
||||
.catch(() => setSuccess(false))
|
||||
} else {
|
||||
setSuccess(false)
|
||||
}
|
||||
}, [library, logoURL, token])
|
||||
|
||||
return { addToken, success }
|
||||
}
|
||||
@@ -48,21 +48,21 @@ export function useContract<T extends Contract = Contract>(
|
||||
ABI: any,
|
||||
withSignerIfPossible = true
|
||||
): T | null {
|
||||
const { library, account, chainId } = useActiveWeb3React()
|
||||
const { provider, account, chainId } = useActiveWeb3React()
|
||||
|
||||
return useMemo(() => {
|
||||
if (!addressOrAddressMap || !ABI || !library || !chainId) return null
|
||||
if (!addressOrAddressMap || !ABI || !provider || !chainId) return null
|
||||
let address: string | undefined
|
||||
if (typeof addressOrAddressMap === 'string') address = addressOrAddressMap
|
||||
else address = addressOrAddressMap[chainId]
|
||||
if (!address) return null
|
||||
try {
|
||||
return getContract(address, ABI, library, withSignerIfPossible && account ? account : undefined)
|
||||
return getContract(address, ABI, provider, withSignerIfPossible && account ? account : undefined)
|
||||
} catch (error) {
|
||||
console.error('Failed to get contract', error)
|
||||
return null
|
||||
}
|
||||
}, [addressOrAddressMap, ABI, library, chainId, withSignerIfPossible, account]) as T
|
||||
}, [addressOrAddressMap, ABI, provider, chainId, withSignerIfPossible, account]) as T
|
||||
}
|
||||
|
||||
export function useV2MigratorContract() {
|
||||
|
||||
@@ -126,7 +126,7 @@ export function useERC20Permit(
|
||||
state: UseERC20PermitState
|
||||
gatherPermitSignature: null | (() => Promise<void>)
|
||||
} {
|
||||
const { account, chainId, library } = useActiveWeb3React()
|
||||
const { account, chainId, provider } = useActiveWeb3React()
|
||||
const tokenAddress = currencyAmount?.currency?.isToken ? currencyAmount.currency.address : undefined
|
||||
const eip2612Contract = useEIP2612Contract(tokenAddress)
|
||||
const isArgentWallet = useIsArgentWallet()
|
||||
@@ -145,7 +145,7 @@ export function useERC20Permit(
|
||||
!account ||
|
||||
!chainId ||
|
||||
!transactionDeadline ||
|
||||
!library ||
|
||||
!provider ||
|
||||
!tokenNonceState.valid ||
|
||||
!tokenAddress ||
|
||||
!spender ||
|
||||
@@ -221,7 +221,7 @@ export function useERC20Permit(
|
||||
message,
|
||||
})
|
||||
|
||||
return library
|
||||
return provider
|
||||
.send('eth_signTypedData_v4', [account, data])
|
||||
.then(splitSignature)
|
||||
.then((signature) => {
|
||||
@@ -248,7 +248,7 @@ export function useERC20Permit(
|
||||
chainId,
|
||||
isArgentWallet,
|
||||
transactionDeadline,
|
||||
library,
|
||||
provider,
|
||||
tokenNonceState.loading,
|
||||
tokenNonceState.valid,
|
||||
tokenNonceState.result,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { skipToken } from '@reduxjs/toolkit/query/react'
|
||||
import { Currency, Token } from '@uniswap/sdk-core'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import useBlockNumber from 'lib/hooks/useBlockNumber'
|
||||
import ms from 'ms.macro'
|
||||
import { useMemo } from 'react'
|
||||
import ReactGA from 'react-ga4'
|
||||
import { useFeeTierDistributionQuery } from 'state/data/enhanced'
|
||||
import { FeeTierDistributionQuery } from 'state/data/generated'
|
||||
|
||||
@@ -112,7 +112,7 @@ function usePoolTVL(token0: Token | undefined, token1: Token | undefined) {
|
||||
}
|
||||
|
||||
if (latestBlock - (_meta?.block?.number ?? 0) > MAX_DATA_BLOCK_AGE) {
|
||||
ReactGA.event('exception', { description: `Graph stale (latest block: ${latestBlock})` })
|
||||
sendEvent('exception', { description: `Graph stale (latest block: ${latestBlock})` })
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
|
||||
@@ -1,39 +1,22 @@
|
||||
import { nanoid } from '@reduxjs/toolkit'
|
||||
import { TokenList } from '@uniswap/token-lists'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { MAINNET_PROVIDER } from 'constants/infura'
|
||||
import getTokenList from 'lib/hooks/useTokenList/fetchTokenList'
|
||||
import resolveENSContentHash from 'lib/utils/resolveENSContentHash'
|
||||
import { useCallback } from 'react'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
|
||||
import { getNetworkLibrary } from '../connectors'
|
||||
import { fetchTokenList } from '../state/lists/actions'
|
||||
|
||||
export function useFetchListCallback(): (listUrl: string, sendDispatch?: boolean) => Promise<TokenList> {
|
||||
const { chainId, library } = useActiveWeb3React()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const ensResolver = useCallback(
|
||||
async (ensName: string) => {
|
||||
if (!library || chainId !== 1) {
|
||||
const networkLibrary = getNetworkLibrary()
|
||||
const network = await networkLibrary.getNetwork()
|
||||
if (networkLibrary && network.chainId === 1) {
|
||||
return resolveENSContentHash(ensName, networkLibrary)
|
||||
}
|
||||
throw new Error('Could not construct mainnet ENS resolver')
|
||||
}
|
||||
return resolveENSContentHash(ensName, library)
|
||||
},
|
||||
[chainId, library]
|
||||
)
|
||||
|
||||
// note: prevent dispatch if using for list search or unsupported list
|
||||
return useCallback(
|
||||
async (listUrl: string, sendDispatch = true) => {
|
||||
const requestId = nanoid()
|
||||
sendDispatch && dispatch(fetchTokenList.pending({ requestId, url: listUrl }))
|
||||
return getTokenList(listUrl, ensResolver)
|
||||
return getTokenList(listUrl, (ensName: string) => resolveENSContentHash(ensName, MAINNET_PROVIDER))
|
||||
.then((tokenList) => {
|
||||
sendDispatch && dispatch(fetchTokenList.fulfilled({ url: listUrl, tokenList, requestId }))
|
||||
return tokenList
|
||||
@@ -44,6 +27,6 @@ export function useFetchListCallback(): (listUrl: string, sendDispatch?: boolean
|
||||
throw error
|
||||
})
|
||||
},
|
||||
[dispatch, ensResolver]
|
||||
[dispatch]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { SupportedLocale } from 'constants/locales'
|
||||
import { LocationDescriptor } from 'history'
|
||||
import useParsedQueryString from 'hooks/useParsedQueryString'
|
||||
import { stringify } from 'qs'
|
||||
import { useMemo } from 'react'
|
||||
import ReactGA from 'react-ga4'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
import { useActiveLocale } from './useActiveLocale'
|
||||
@@ -26,7 +26,7 @@ export function useLocationLinkProps(locale: SupportedLocale | null): {
|
||||
search: stringify({ ...qs, lng: locale }),
|
||||
},
|
||||
onClick: () => {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Localization',
|
||||
action: 'Switch Locale',
|
||||
label: `${activeLocale} -> ${locale}`,
|
||||
|
||||
@@ -39,7 +39,7 @@ export function useSwapCallArguments(
|
||||
deadline: BigNumber | undefined,
|
||||
feeOptions: FeeOptions | undefined
|
||||
): SwapCall[] {
|
||||
const { account, chainId, library } = useActiveWeb3React()
|
||||
const { account, chainId, provider } = useActiveWeb3React()
|
||||
|
||||
const { address: recipientAddress } = useENS(recipientAddressOrName)
|
||||
const recipient = recipientAddressOrName === null ? account : recipientAddress
|
||||
@@ -47,7 +47,7 @@ export function useSwapCallArguments(
|
||||
const argentWalletContract = useArgentWalletContract()
|
||||
|
||||
return useMemo(() => {
|
||||
if (!trade || !recipient || !library || !account || !chainId || !deadline) return []
|
||||
if (!trade || !recipient || !provider || !account || !chainId || !deadline) return []
|
||||
|
||||
if (trade instanceof V2Trade) {
|
||||
if (!routerContract) return []
|
||||
@@ -175,7 +175,7 @@ export function useSwapCallArguments(
|
||||
chainId,
|
||||
deadline,
|
||||
feeOptions,
|
||||
library,
|
||||
provider,
|
||||
recipient,
|
||||
routerContract,
|
||||
signatureData,
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
import type { EthereumProvider } from 'lib/ethereum'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useWeb3React } from 'web3-react-core'
|
||||
|
||||
import { gnosisSafe, injected } from '../connectors'
|
||||
import { IS_IN_IFRAME } from '../constants/misc'
|
||||
import { isMobile } from '../utils/userAgent'
|
||||
|
||||
export function useEagerConnect() {
|
||||
const { activate, active } = useWeb3React()
|
||||
const [tried, setTried] = useState(false)
|
||||
|
||||
// gnosisSafe.isSafeApp() races a timeout against postMessage, so it delays pageload if we are not in a safe app;
|
||||
// if we are not embedded in an iframe, it is not worth checking
|
||||
const [triedSafe, setTriedSafe] = useState(!IS_IN_IFRAME)
|
||||
|
||||
// first, try connecting to a gnosis safe
|
||||
useEffect(() => {
|
||||
if (!triedSafe) {
|
||||
gnosisSafe.isSafeApp().then((loadedInSafe) => {
|
||||
if (loadedInSafe) {
|
||||
activate(gnosisSafe, undefined, true).catch(() => {
|
||||
setTriedSafe(true)
|
||||
})
|
||||
} else {
|
||||
setTriedSafe(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [activate, setTriedSafe, triedSafe])
|
||||
|
||||
// then, if that fails, try connecting to an injected connector
|
||||
useEffect(() => {
|
||||
if (!active && triedSafe) {
|
||||
injected.isAuthorized().then((isAuthorized) => {
|
||||
if (isAuthorized) {
|
||||
activate(injected, undefined, true).catch(() => {
|
||||
setTried(true)
|
||||
})
|
||||
} else {
|
||||
if (isMobile && window.ethereum) {
|
||||
activate(injected, undefined, true).catch(() => {
|
||||
setTried(true)
|
||||
})
|
||||
} else {
|
||||
setTried(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [activate, active, triedSafe])
|
||||
|
||||
// wait until we get confirmation of a connection to flip the flag
|
||||
useEffect(() => {
|
||||
if (active) {
|
||||
setTried(true)
|
||||
}
|
||||
}, [active])
|
||||
|
||||
return tried
|
||||
}
|
||||
|
||||
/**
|
||||
* Use for network and injected - logs user in
|
||||
* and out after checking what network theyre on
|
||||
*/
|
||||
export function useInactiveListener(suppress = false) {
|
||||
const { active, error, activate } = useWeb3React()
|
||||
|
||||
useEffect(() => {
|
||||
const ethereum = window.ethereum as EthereumProvider | undefined
|
||||
|
||||
if (ethereum && ethereum.on && !active && !error && !suppress) {
|
||||
const handleChainChanged = () => {
|
||||
// eat errors
|
||||
activate(injected, undefined, true).catch((error) => {
|
||||
console.error('Failed to activate after chain changed', error)
|
||||
})
|
||||
}
|
||||
|
||||
const handleAccountsChanged = (accounts: string[]) => {
|
||||
if (accounts.length > 0) {
|
||||
// eat errors
|
||||
activate(injected, undefined, true).catch((error) => {
|
||||
console.error('Failed to activate after accounts changed', error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ethereum.on('chainChanged', handleChainChanged)
|
||||
ethereum.on('accountsChanged', handleAccountsChanged)
|
||||
|
||||
return () => {
|
||||
if (ethereum.removeListener) {
|
||||
ethereum.removeListener('chainChanged', handleChainChanged)
|
||||
ethereum.removeListener('accountsChanged', handleAccountsChanged)
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}, [active, error, suppress, activate])
|
||||
}
|
||||
@@ -9,10 +9,9 @@ import { StrictMode } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { Provider } from 'react-redux'
|
||||
import { HashRouter } from 'react-router-dom'
|
||||
import { createWeb3ReactRoot, Web3ReactProvider } from 'web3-react-core'
|
||||
|
||||
import Blocklist from './components/Blocklist'
|
||||
import { NetworkContextName } from './constants/misc'
|
||||
import Web3Provider from './components/Web3Provider'
|
||||
import { LanguageProvider } from './i18n'
|
||||
import App from './pages/App'
|
||||
import * as serviceWorkerRegistration from './serviceWorkerRegistration'
|
||||
@@ -24,9 +23,6 @@ import TransactionUpdater from './state/transactions/updater'
|
||||
import UserUpdater from './state/user/updater'
|
||||
import ThemeProvider, { ThemedGlobalStyle } from './theme'
|
||||
import RadialGradientByChainUpdater from './theme/RadialGradientByChainUpdater'
|
||||
import getLibrary from './utils/getLibrary'
|
||||
|
||||
const Web3ProviderNetwork = createWeb3ReactRoot(NetworkContextName)
|
||||
|
||||
if (!!window.ethereum) {
|
||||
window.ethereum.autoRefreshOnNetworkChange = false
|
||||
@@ -51,19 +47,17 @@ ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<HashRouter>
|
||||
<LanguageProvider>
|
||||
<Web3ReactProvider getLibrary={getLibrary}>
|
||||
<Web3ProviderNetwork getLibrary={getLibrary}>
|
||||
<Blocklist>
|
||||
<BlockNumberProvider>
|
||||
<Updaters />
|
||||
<ThemeProvider>
|
||||
<ThemedGlobalStyle />
|
||||
<App />
|
||||
</ThemeProvider>
|
||||
</BlockNumberProvider>
|
||||
</Blocklist>
|
||||
</Web3ProviderNetwork>
|
||||
</Web3ReactProvider>
|
||||
<Web3Provider>
|
||||
<Blocklist>
|
||||
<BlockNumberProvider>
|
||||
<Updaters />
|
||||
<ThemeProvider>
|
||||
<ThemedGlobalStyle />
|
||||
<App />
|
||||
</ThemeProvider>
|
||||
</BlockNumberProvider>
|
||||
</Blocklist>
|
||||
</Web3Provider>
|
||||
</LanguageProvider>
|
||||
</HashRouter>
|
||||
</Provider>
|
||||
|
||||
@@ -42,7 +42,7 @@ export function useSwapCallback({
|
||||
deadline,
|
||||
feeOptions,
|
||||
}: UseSwapCallbackArgs): UseSwapCallbackReturns {
|
||||
const { account, chainId, library } = useActiveWeb3React()
|
||||
const { account, chainId, provider } = useActiveWeb3React()
|
||||
|
||||
const swapCalls = useSwapCallArguments(
|
||||
trade,
|
||||
@@ -52,13 +52,13 @@ export function useSwapCallback({
|
||||
deadline,
|
||||
feeOptions
|
||||
)
|
||||
const { callback } = useSendSwapTransaction(account, chainId, library, trade, swapCalls)
|
||||
const { callback } = useSendSwapTransaction(account, chainId, provider, trade, swapCalls)
|
||||
|
||||
const { address: recipientAddress } = useENS(recipientAddressOrName)
|
||||
const recipient = recipientAddressOrName === null ? account : recipientAddress
|
||||
|
||||
return useMemo(() => {
|
||||
if (!trade || !library || !account || !chainId || !callback) {
|
||||
if (!trade || !provider || !account || !chainId || !callback) {
|
||||
return { state: SwapCallbackState.INVALID, error: <Trans>Missing dependencies</Trans> }
|
||||
}
|
||||
if (!recipient) {
|
||||
@@ -73,5 +73,5 @@ export function useSwapCallback({
|
||||
state: SwapCallbackState.VALID,
|
||||
callback: async () => callback(),
|
||||
}
|
||||
}, [trade, library, account, chainId, callback, recipient, recipientAddressOrName])
|
||||
}, [trade, provider, account, chainId, callback, recipient, recipientAddressOrName])
|
||||
}
|
||||
|
||||
@@ -45,18 +45,18 @@ interface UpdaterProps {
|
||||
}
|
||||
|
||||
export default function Updater({ pendingTransactions, onCheck, onReceipt }: UpdaterProps): null {
|
||||
const { chainId, library } = useActiveWeb3React()
|
||||
const { chainId, provider } = useActiveWeb3React()
|
||||
|
||||
const lastBlockNumber = useBlockNumber()
|
||||
const fastForwardBlockNumber = useFastForwardBlockNumber()
|
||||
|
||||
const getReceipt = useCallback(
|
||||
(hash: string) => {
|
||||
if (!library || !chainId) throw new Error('No library or chainId')
|
||||
if (!provider || !chainId) throw new Error('No provider or chainId')
|
||||
const retryOptions = RETRY_OPTIONS_BY_CHAIN_ID[chainId] ?? DEFAULT_RETRY_OPTIONS
|
||||
return retry(
|
||||
() =>
|
||||
library.getTransactionReceipt(hash).then((receipt) => {
|
||||
provider.getTransactionReceipt(hash).then((receipt) => {
|
||||
if (receipt === null) {
|
||||
console.debug(`Retrying tranasaction receipt for ${hash}`)
|
||||
throw new RetryableError()
|
||||
@@ -66,11 +66,11 @@ export default function Updater({ pendingTransactions, onCheck, onReceipt }: Upd
|
||||
retryOptions
|
||||
)
|
||||
},
|
||||
[chainId, library]
|
||||
[chainId, provider]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!chainId || !library || !lastBlockNumber) return
|
||||
if (!chainId || !provider || !lastBlockNumber) return
|
||||
|
||||
const cancels = Object.keys(pendingTransactions)
|
||||
.filter((hash) => shouldCheck(lastBlockNumber, pendingTransactions[hash]))
|
||||
@@ -95,7 +95,7 @@ export default function Updater({ pendingTransactions, onCheck, onReceipt }: Upd
|
||||
return () => {
|
||||
cancels.forEach((cancel) => cancel())
|
||||
}
|
||||
}, [chainId, library, lastBlockNumber, getReceipt, fastForwardBlockNumber, onReceipt, onCheck, pendingTransactions])
|
||||
}, [chainId, provider, lastBlockNumber, getReceipt, fastForwardBlockNumber, onReceipt, onCheck, pendingTransactions])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ export function useFastForwardBlockNumber(): (block: number) => void {
|
||||
}
|
||||
|
||||
export function BlockNumberProvider({ children }: { children: ReactNode }) {
|
||||
const { chainId: activeChainId, library } = useActiveWeb3React()
|
||||
const { chainId: activeChainId, provider } = useActiveWeb3React()
|
||||
const [{ chainId, block }, setChainBlock] = useState<{ chainId?: number; block?: number }>({ chainId: activeChainId })
|
||||
|
||||
const onBlock = useCallback(
|
||||
@@ -48,24 +48,24 @@ export function BlockNumberProvider({ children }: { children: ReactNode }) {
|
||||
|
||||
const windowVisible = useIsWindowVisible()
|
||||
useEffect(() => {
|
||||
if (library && activeChainId && windowVisible) {
|
||||
if (provider && activeChainId && windowVisible) {
|
||||
// If chainId hasn't changed, don't clear the block. This prevents re-fetching still valid data.
|
||||
setChainBlock((chainBlock) => (chainBlock.chainId === activeChainId ? chainBlock : { chainId: activeChainId }))
|
||||
|
||||
library
|
||||
provider
|
||||
.getBlockNumber()
|
||||
.then(onBlock)
|
||||
.catch((error) => {
|
||||
console.error(`Failed to get block number for chainId ${activeChainId}`, error)
|
||||
})
|
||||
|
||||
library.on('block', onBlock)
|
||||
provider.on('block', onBlock)
|
||||
return () => {
|
||||
library.removeListener('block', onBlock)
|
||||
provider.removeListener('block', onBlock)
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}, [activeChainId, library, onBlock, setChainBlock, windowVisible])
|
||||
}, [activeChainId, provider, onBlock, setChainBlock, windowVisible])
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useBytes32TokenContract, useTokenContract } from 'hooks/useContract'
|
||||
import { NEVER_RELOAD, useSingleCallResult } from 'lib/hooks/multicall'
|
||||
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
import { useMemo } from 'react'
|
||||
import { isChainAllowed } from 'utils/switchChain'
|
||||
|
||||
import { TOKEN_SHORTHANDS } from '../../constants/tokens'
|
||||
import { isAddress } from '../../utils'
|
||||
@@ -29,7 +30,8 @@ function parseStringOrBytes32(str: string | undefined, bytes32: string | undefin
|
||||
* Returns undefined if tokenAddress is invalid or token does not exist.
|
||||
*/
|
||||
export function useTokenFromNetwork(tokenAddress: string | null | undefined): Token | null | undefined {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId, connector } = useActiveWeb3React()
|
||||
const chainAllowed = chainId && isChainAllowed(connector, chainId)
|
||||
|
||||
const formattedAddress = isAddress(tokenAddress)
|
||||
|
||||
@@ -43,7 +45,7 @@ export function useTokenFromNetwork(tokenAddress: string | null | undefined): To
|
||||
const decimals = useSingleCallResult(tokenContract, 'decimals', undefined, NEVER_RELOAD)
|
||||
|
||||
return useMemo(() => {
|
||||
if (typeof tokenAddress !== 'string' || !chainId || !formattedAddress) return undefined
|
||||
if (typeof tokenAddress !== 'string' || !chainAllowed || !formattedAddress) return undefined
|
||||
if (decimals.loading || symbol.loading || tokenName.loading) return null
|
||||
if (decimals.result) {
|
||||
return new Token(
|
||||
@@ -58,6 +60,7 @@ export function useTokenFromNetwork(tokenAddress: string | null | undefined): To
|
||||
}, [
|
||||
formattedAddress,
|
||||
chainId,
|
||||
chainAllowed,
|
||||
decimals.loading,
|
||||
decimals.result,
|
||||
symbol.loading,
|
||||
@@ -93,7 +96,7 @@ export function useTokenFromMapOrNetwork(tokens: TokenMap, tokenAddress?: string
|
||||
*/
|
||||
export function useCurrencyFromMap(tokens: TokenMap, currencyId?: string | null): Currency | null | undefined {
|
||||
const nativeCurrency = useNativeCurrency()
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId, connector } = useActiveWeb3React()
|
||||
const isNative = Boolean(nativeCurrency && currencyId?.toUpperCase() === 'ETH')
|
||||
const shorthandMatchAddress = useMemo(() => {
|
||||
const chain = supportedChainId(chainId)
|
||||
@@ -102,7 +105,8 @@ export function useCurrencyFromMap(tokens: TokenMap, currencyId?: string | null)
|
||||
|
||||
const token = useTokenFromMapOrNetwork(tokens, isNative ? undefined : shorthandMatchAddress ?? currencyId)
|
||||
|
||||
if (currencyId === null || currencyId === undefined) return currencyId
|
||||
const chainAllowed = chainId && isChainAllowed(connector, chainId)
|
||||
if (currencyId === null || currencyId === undefined || !chainAllowed) return null
|
||||
|
||||
// this case so we use our builtin wrapped token instead of wrapped tokens on token lists
|
||||
const wrappedNative = nativeCurrency?.wrapped
|
||||
|
||||
@@ -16,16 +16,20 @@ export function tokensToChainTokenMap(tokens: TokenList | TokenInfo[]): ChainTok
|
||||
|
||||
const [list, infos] = Array.isArray(tokens) ? [undefined, tokens] : [tokens, tokens.tokens]
|
||||
const map = infos.reduce<Mutable<ChainTokenMap>>((map, info) => {
|
||||
const token = new WrappedTokenInfo(info, list)
|
||||
if (map[token.chainId]?.[token.address] !== undefined) {
|
||||
console.warn(`Duplicate token skipped: ${token.address}`)
|
||||
try {
|
||||
const token = new WrappedTokenInfo(info, list)
|
||||
if (map[token.chainId]?.[token.address] !== undefined) {
|
||||
console.warn(`Duplicate token skipped: ${token.address}`)
|
||||
return map
|
||||
}
|
||||
if (!map[token.chainId]) {
|
||||
map[token.chainId] = {}
|
||||
}
|
||||
map[token.chainId][token.address] = { token, list }
|
||||
return map
|
||||
} catch {
|
||||
return map
|
||||
}
|
||||
if (!map[token.chainId]) {
|
||||
map[token.chainId] = {}
|
||||
}
|
||||
map[token.chainId][token.address] = { token, list }
|
||||
return map
|
||||
}, {}) as ChainTokenMap
|
||||
mapCache?.set(tokens, map)
|
||||
return map
|
||||
|
||||
@@ -3,12 +3,12 @@ import { TransactionResponse } from '@ethersproject/providers'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
|
||||
import { FeeAmount, NonfungiblePositionManager } from '@uniswap/v3-sdk'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import useParsedQueryString from 'hooks/useParsedQueryString'
|
||||
import { useCallback, useContext, useEffect, useState } from 'react'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import ReactGA from 'react-ga4'
|
||||
import { RouteComponentProps } from 'react-router-dom'
|
||||
import { Text } from 'rebass'
|
||||
import {
|
||||
@@ -81,7 +81,7 @@ export default function AddLiquidity({
|
||||
},
|
||||
history,
|
||||
}: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string; feeAmount?: string; tokenId?: string }>) {
|
||||
const { account, chainId, library } = useActiveWeb3React()
|
||||
const { account, chainId, provider } = useActiveWeb3React()
|
||||
const theme = useContext(ThemeContext)
|
||||
const toggleWalletModal = useWalletModalToggle() // toggle wallet when disconnected
|
||||
const expertMode = useIsExpertMode()
|
||||
@@ -225,7 +225,7 @@ export default function AddLiquidity({
|
||||
)
|
||||
|
||||
async function onAdd() {
|
||||
if (!chainId || !library || !account) return
|
||||
if (!chainId || !provider || !account) return
|
||||
|
||||
if (!positionManager || !baseCurrency || !quoteCurrency) {
|
||||
return
|
||||
@@ -281,7 +281,7 @@ export default function AddLiquidity({
|
||||
|
||||
setAttemptingTxn(true)
|
||||
|
||||
library
|
||||
provider
|
||||
.getSigner()
|
||||
.estimateGas(txn)
|
||||
.then((estimate) => {
|
||||
@@ -290,7 +290,7 @@ export default function AddLiquidity({
|
||||
gasLimit: calculateGasMargin(estimate),
|
||||
}
|
||||
|
||||
return library
|
||||
return provider
|
||||
.getSigner()
|
||||
.sendTransaction(newTxn)
|
||||
.then((response: TransactionResponse) => {
|
||||
@@ -305,7 +305,7 @@ export default function AddLiquidity({
|
||||
feeAmount: position.pool.fee,
|
||||
})
|
||||
setTxHash(response.hash)
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Liquidity',
|
||||
action: 'Add',
|
||||
label: [currencies[Field.CURRENCY_A]?.symbol, currencies[Field.CURRENCY_B]?.symbol].join('/'),
|
||||
|
||||
@@ -2,12 +2,12 @@ import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { TransactionResponse } from '@ethersproject/providers'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
|
||||
import { SwitchLocaleLink } from 'components/SwitchLocaleLink'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useCallback, useContext, useState } from 'react'
|
||||
import { Plus } from 'react-feather'
|
||||
import ReactGA from 'react-ga4'
|
||||
import { RouteComponentProps } from 'react-router-dom'
|
||||
import { Text } from 'rebass'
|
||||
import { ThemeContext } from 'styled-components/macro'
|
||||
@@ -53,7 +53,7 @@ export default function AddLiquidity({
|
||||
},
|
||||
history,
|
||||
}: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string }>) {
|
||||
const { account, chainId, library } = useActiveWeb3React()
|
||||
const { account, chainId, provider } = useActiveWeb3React()
|
||||
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
@@ -137,7 +137,7 @@ export default function AddLiquidity({
|
||||
const addTransaction = useTransactionAdder()
|
||||
|
||||
async function onAdd() {
|
||||
if (!chainId || !library || !account || !router) return
|
||||
if (!chainId || !provider || !account || !router) return
|
||||
|
||||
const { [Field.CURRENCY_A]: parsedAmountA, [Field.CURRENCY_B]: parsedAmountB } = parsedAmounts
|
||||
if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB || !deadline) {
|
||||
@@ -201,7 +201,7 @@ export default function AddLiquidity({
|
||||
|
||||
setTxHash(response.hash)
|
||||
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Liquidity',
|
||||
action: 'Add',
|
||||
label: [currencies[Field.CURRENCY_A]?.symbol, currencies[Field.CURRENCY_B]?.symbol].join('/'),
|
||||
|
||||
@@ -2,15 +2,15 @@ import Loader from 'components/Loader'
|
||||
import TopLevelModals from 'components/TopLevelModals'
|
||||
import ApeModeQueryParamReader from 'hooks/useApeModeQueryParamReader'
|
||||
import { lazy, Suspense } from 'react'
|
||||
import { Redirect, Route, Switch } from 'react-router-dom'
|
||||
import { useEffect } from 'react'
|
||||
import { Redirect, Route, Switch, useHistory, useLocation } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import GoogleAnalyticsReporter from '../components/analytics/GoogleAnalyticsReporter'
|
||||
import { useAnalyticsReporter } from '../components/analytics'
|
||||
import ErrorBoundary from '../components/ErrorBoundary'
|
||||
import Header from '../components/Header'
|
||||
import Polling from '../components/Header/Polling'
|
||||
import Popups from '../components/Popups'
|
||||
import Web3ReactManager from '../components/Web3ReactManager'
|
||||
import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader'
|
||||
import AddLiquidity from './AddLiquidity'
|
||||
import { RedirectDuplicateTokenIds } from './AddLiquidity/redirects'
|
||||
@@ -64,72 +64,76 @@ const Marginer = styled.div`
|
||||
`
|
||||
|
||||
export default function App() {
|
||||
const history = useHistory()
|
||||
useAnalyticsReporter(useLocation())
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten = history.listen(() => {
|
||||
window.scrollTo(0, 0)
|
||||
})
|
||||
return () => {
|
||||
unlisten()
|
||||
}
|
||||
}, [history])
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<Route component={GoogleAnalyticsReporter} />
|
||||
<Route component={DarkModeQueryParamReader} />
|
||||
<Route component={ApeModeQueryParamReader} />
|
||||
<Web3ReactManager>
|
||||
<AppWrapper>
|
||||
<HeaderWrapper>
|
||||
<Header />
|
||||
</HeaderWrapper>
|
||||
<BodyWrapper>
|
||||
<Popups />
|
||||
<Polling />
|
||||
<TopLevelModals />
|
||||
<Suspense fallback={<Loader />}>
|
||||
<Switch>
|
||||
<Route strict path="/vote" component={Vote} />
|
||||
<Route exact strict path="/create-proposal">
|
||||
<Redirect to="/vote/create-proposal" />
|
||||
</Route>
|
||||
<Route exact strict path="/claim" component={OpenClaimAddressModalAndRedirectToSwap} />
|
||||
<Route exact strict path="/uni" component={Earn} />
|
||||
<Route exact strict path="/uni/:currencyIdA/:currencyIdB" component={Manage} />
|
||||
<AppWrapper>
|
||||
<HeaderWrapper>
|
||||
<Header />
|
||||
</HeaderWrapper>
|
||||
<BodyWrapper>
|
||||
<Popups />
|
||||
<Polling />
|
||||
<TopLevelModals />
|
||||
<Suspense fallback={<Loader />}>
|
||||
<Switch>
|
||||
<Route strict path="/vote" component={Vote} />
|
||||
<Route exact strict path="/create-proposal">
|
||||
<Redirect to="/vote/create-proposal" />
|
||||
</Route>
|
||||
<Route exact strict path="/claim" component={OpenClaimAddressModalAndRedirectToSwap} />
|
||||
<Route exact strict path="/uni" component={Earn} />
|
||||
<Route exact strict path="/uni/:currencyIdA/:currencyIdB" component={Manage} />
|
||||
|
||||
<Route exact strict path="/send" component={RedirectPathToSwapOnly} />
|
||||
<Route exact strict path="/swap/:outputCurrency" component={RedirectToSwap} />
|
||||
<Route exact strict path="/swap" component={Swap} />
|
||||
<Route exact strict path="/send" component={RedirectPathToSwapOnly} />
|
||||
<Route exact strict path="/swap/:outputCurrency" component={RedirectToSwap} />
|
||||
<Route exact strict path="/swap" component={Swap} />
|
||||
|
||||
<Route exact strict path="/pool/v2/find" component={PoolFinder} />
|
||||
<Route exact strict path="/pool/v2" component={PoolV2} />
|
||||
<Route exact strict path="/pool" component={Pool} />
|
||||
<Route exact strict path="/pool/:tokenId" component={PositionPage} />
|
||||
<Route exact strict path="/pool/v2/find" component={PoolFinder} />
|
||||
<Route exact strict path="/pool/v2" component={PoolV2} />
|
||||
<Route exact strict path="/pool" component={Pool} />
|
||||
<Route exact strict path="/pool/:tokenId" component={PositionPage} />
|
||||
|
||||
<Route
|
||||
exact
|
||||
strict
|
||||
path="/add/v2/:currencyIdA?/:currencyIdB?"
|
||||
component={RedirectDuplicateTokenIdsV2}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
strict
|
||||
path="/add/:currencyIdA?/:currencyIdB?/:feeAmount?"
|
||||
component={RedirectDuplicateTokenIds}
|
||||
/>
|
||||
<Route exact strict path="/add/v2/:currencyIdA?/:currencyIdB?" component={RedirectDuplicateTokenIdsV2} />
|
||||
<Route
|
||||
exact
|
||||
strict
|
||||
path="/add/:currencyIdA?/:currencyIdB?/:feeAmount?"
|
||||
component={RedirectDuplicateTokenIds}
|
||||
/>
|
||||
|
||||
<Route
|
||||
exact
|
||||
strict
|
||||
path="/increase/:currencyIdA?/:currencyIdB?/:feeAmount?/:tokenId?"
|
||||
component={AddLiquidity}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
strict
|
||||
path="/increase/:currencyIdA?/:currencyIdB?/:feeAmount?/:tokenId?"
|
||||
component={AddLiquidity}
|
||||
/>
|
||||
|
||||
<Route exact strict path="/remove/v2/:currencyIdA/:currencyIdB" component={RemoveLiquidity} />
|
||||
<Route exact strict path="/remove/:tokenId" component={RemoveLiquidityV3} />
|
||||
<Route exact strict path="/remove/v2/:currencyIdA/:currencyIdB" component={RemoveLiquidity} />
|
||||
<Route exact strict path="/remove/:tokenId" component={RemoveLiquidityV3} />
|
||||
|
||||
<Route exact strict path="/migrate/v2" component={MigrateV2} />
|
||||
<Route exact strict path="/migrate/v2/:address" component={MigrateV2Pair} />
|
||||
<Route exact strict path="/migrate/v2" component={MigrateV2} />
|
||||
<Route exact strict path="/migrate/v2/:address" component={MigrateV2Pair} />
|
||||
|
||||
<Route component={RedirectPathToSwapOnly} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
<Marginer />
|
||||
</BodyWrapper>
|
||||
</AppWrapper>
|
||||
</Web3ReactManager>
|
||||
<Route component={RedirectPathToSwapOnly} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
<Marginer />
|
||||
</BodyWrapper>
|
||||
</AppWrapper>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { TransactionResponse } from '@ethersproject/providers'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { CurrencyAmount, Fraction, Percent, Price, Token } from '@uniswap/sdk-core'
|
||||
import { FeeAmount, Pool, Position, priceToClosestTick, TickMath } from '@uniswap/v3-sdk'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import Badge, { BadgeVariant } from 'components/Badge'
|
||||
import { ButtonConfirmed } from 'components/Button'
|
||||
import { BlueCard, DarkGreyCard, LightCard, YellowCard } from 'components/Card'
|
||||
@@ -23,7 +24,6 @@ import JSBI from 'jsbi'
|
||||
import { NEVER_RELOAD, useSingleCallResult } from 'lib/hooks/multicall'
|
||||
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { AlertCircle, AlertTriangle, ArrowDown } from 'react-feather'
|
||||
import ReactGA from 'react-ga4'
|
||||
import { Redirect, RouteComponentProps } from 'react-router'
|
||||
import { Text } from 'rebass'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
@@ -336,7 +336,7 @@ function V2PairMigration({
|
||||
return migrator
|
||||
.multicall(data, { gasLimit: calculateGasMargin(gasEstimate) })
|
||||
.then((response: TransactionResponse) => {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Migrate',
|
||||
action: `${isNotUniswap ? 'SushiSwap' : 'V2'}->V3`,
|
||||
label: `${currency0.symbol}/${currency1.symbol}`,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { TransactionResponse } from '@ethersproject/providers'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, CurrencyAmount, Fraction, Percent, Price, Token } from '@uniswap/sdk-core'
|
||||
import { NonfungiblePositionManager, Pool, Position } from '@uniswap/v3-sdk'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import Badge from 'components/Badge'
|
||||
import { ButtonConfirmed, ButtonGray, ButtonPrimary } from 'components/Button'
|
||||
import { DarkCard, LightCard } from 'components/Card'
|
||||
@@ -25,7 +26,6 @@ import { useV3PositionFromTokenId } from 'hooks/useV3Positions'
|
||||
import { useSingleCallResult } from 'lib/hooks/multicall'
|
||||
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
import { useCallback, useMemo, useRef, useState } from 'react'
|
||||
import ReactGA from 'react-ga4'
|
||||
import { Link, RouteComponentProps } from 'react-router-dom'
|
||||
import { Bound } from 'state/mint/v3/actions'
|
||||
import { useIsTransactionPending, useTransactionAdder } from 'state/transactions/hooks'
|
||||
@@ -318,7 +318,7 @@ export function PositionPage({
|
||||
params: { tokenId: tokenIdFromUrl },
|
||||
},
|
||||
}: RouteComponentProps<{ tokenId?: string }>) {
|
||||
const { chainId, account, library } = useActiveWeb3React()
|
||||
const { chainId, account, provider } = useActiveWeb3React()
|
||||
const theme = useTheme()
|
||||
|
||||
const parsedTokenId = tokenIdFromUrl ? BigNumber.from(tokenIdFromUrl) : undefined
|
||||
@@ -433,7 +433,7 @@ export function PositionPage({
|
||||
!positionManager ||
|
||||
!account ||
|
||||
!tokenId ||
|
||||
!library
|
||||
!provider
|
||||
)
|
||||
return
|
||||
|
||||
@@ -454,7 +454,7 @@ export function PositionPage({
|
||||
value,
|
||||
}
|
||||
|
||||
library
|
||||
provider
|
||||
.getSigner()
|
||||
.estimateGas(txn)
|
||||
.then((estimate) => {
|
||||
@@ -463,14 +463,14 @@ export function PositionPage({
|
||||
gasLimit: calculateGasMargin(estimate),
|
||||
}
|
||||
|
||||
return library
|
||||
return provider
|
||||
.getSigner()
|
||||
.sendTransaction(newTxn)
|
||||
.then((response: TransactionResponse) => {
|
||||
setCollectMigrationHash(response.hash)
|
||||
setCollecting(false)
|
||||
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Liquidity',
|
||||
action: 'CollectV3',
|
||||
label: [currency0ForFeeCollectionPurposes.symbol, currency1ForFeeCollectionPurposes.symbol].join('/'),
|
||||
@@ -497,7 +497,7 @@ export function PositionPage({
|
||||
account,
|
||||
tokenId,
|
||||
addTransaction,
|
||||
library,
|
||||
provider,
|
||||
])
|
||||
|
||||
const owner = useSingleCallResult(!!tokenId ? positionManager : null, 'ownerOf', [tokenId]).result?.[0]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { L2_CHAIN_IDS } from 'constants/chains'
|
||||
import { UNSUPPORTED_V2POOL_CHAIN_IDS } from 'constants/chains'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import JSBI from 'jsbi'
|
||||
import { useContext, useMemo } from 'react'
|
||||
@@ -85,9 +85,11 @@ const Layer2Prompt = styled(EmptyProposals)`
|
||||
export default function Pool() {
|
||||
const theme = useContext(ThemeContext)
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
const unsupportedV2Network = chainId && UNSUPPORTED_V2POOL_CHAIN_IDS.includes(chainId)
|
||||
|
||||
// fetch the user's balances of all tracked V2 LP tokens
|
||||
const trackedTokenPairs = useTrackedTokenPairs()
|
||||
let trackedTokenPairs = useTrackedTokenPairs()
|
||||
if (unsupportedV2Network) trackedTokenPairs = []
|
||||
const tokenPairsWithLiquidityTokens = useMemo(
|
||||
() => trackedTokenPairs.map((tokens) => ({ liquidityToken: toV2LiquidityToken(tokens), tokens })),
|
||||
[trackedTokenPairs]
|
||||
@@ -132,8 +134,6 @@ export default function Pool() {
|
||||
)
|
||||
})
|
||||
|
||||
const ON_L2 = chainId && L2_CHAIN_IDS.includes(chainId)
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageWrapper>
|
||||
@@ -171,12 +171,12 @@ export default function Pool() {
|
||||
<CardNoise />
|
||||
</VoteCard>
|
||||
|
||||
{ON_L2 ? (
|
||||
{unsupportedV2Network ? (
|
||||
<AutoColumn gap="lg" justify="center">
|
||||
<AutoColumn gap="md" style={{ width: '100%' }}>
|
||||
<Layer2Prompt>
|
||||
<ThemedText.Body color={theme.text3} textAlign="center">
|
||||
<Trans>V2 is not available on Layer 2. Switch to Layer 1 Ethereum.</Trans>
|
||||
<Trans>V2 Pool is not available on Layer 2. Switch to Layer 1 Ethereum.</Trans>
|
||||
</ThemedText.Body>
|
||||
</Layer2Prompt>
|
||||
</AutoColumn>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { TransactionResponse } from '@ethersproject/providers'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { CurrencyAmount, Percent } from '@uniswap/sdk-core'
|
||||
import { NonfungiblePositionManager } from '@uniswap/v3-sdk'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import RangeBadge from 'components/Badge/RangeBadge'
|
||||
import { ButtonConfirmed, ButtonPrimary } from 'components/Button'
|
||||
import { LightCard } from 'components/Card'
|
||||
@@ -24,7 +25,6 @@ import useTransactionDeadline from 'hooks/useTransactionDeadline'
|
||||
import { useV3PositionFromTokenId } from 'hooks/useV3Positions'
|
||||
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import ReactGA from 'react-ga4'
|
||||
import { Redirect, RouteComponentProps } from 'react-router-dom'
|
||||
import { Text } from 'rebass'
|
||||
import { useBurnV3ActionHandlers, useBurnV3State, useDerivedV3BurnInfo } from 'state/burn/v3/hooks'
|
||||
@@ -66,7 +66,7 @@ export default function RemoveLiquidityV3({
|
||||
function Remove({ tokenId }: { tokenId: BigNumber }) {
|
||||
const { position } = useV3PositionFromTokenId(tokenId)
|
||||
const theme = useTheme()
|
||||
const { account, chainId, library } = useActiveWeb3React()
|
||||
const { account, chainId, provider } = useActiveWeb3React()
|
||||
|
||||
// flag for receiving WETH
|
||||
const [receiveWETH, setReceiveWETH] = useState(false)
|
||||
@@ -111,7 +111,7 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
|
||||
!chainId ||
|
||||
!positionSDK ||
|
||||
!liquidityPercentage ||
|
||||
!library
|
||||
!provider
|
||||
) {
|
||||
return
|
||||
}
|
||||
@@ -136,7 +136,7 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
|
||||
value,
|
||||
}
|
||||
|
||||
library
|
||||
provider
|
||||
.getSigner()
|
||||
.estimateGas(txn)
|
||||
.then((estimate) => {
|
||||
@@ -145,11 +145,11 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
|
||||
gasLimit: calculateGasMargin(estimate),
|
||||
}
|
||||
|
||||
return library
|
||||
return provider
|
||||
.getSigner()
|
||||
.sendTransaction(newTxn)
|
||||
.then((response: TransactionResponse) => {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Liquidity',
|
||||
action: 'RemoveV3',
|
||||
label: [liquidityValue0.currency.symbol, liquidityValue1.currency.symbol].join('/'),
|
||||
@@ -180,7 +180,7 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
|
||||
feeValue1,
|
||||
positionSDK,
|
||||
liquidityPercentage,
|
||||
library,
|
||||
provider,
|
||||
tokenId,
|
||||
allowedSlippage,
|
||||
addTransaction,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user