Compare commits
84 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ba6275b71 | ||
|
|
17c7a9ee9d | ||
|
|
0e36944b23 | ||
|
|
5050fe7b06 | ||
|
|
80b965f2ca | ||
|
|
417e940c0a | ||
|
|
c42a5ccf26 | ||
|
|
7ba9b1faf6 | ||
|
|
5d43d08ff3 | ||
|
|
85625d09f0 | ||
|
|
d85f1f44b9 | ||
|
|
f41580e43c | ||
|
|
47fe9911fa | ||
|
|
e0f6d82d6c | ||
|
|
fb691cf17b | ||
|
|
f596293b6c | ||
|
|
8012789f69 | ||
|
|
4013743473 | ||
|
|
e9ef3193ab | ||
|
|
6a1506ade6 | ||
|
|
839d4ac8e2 | ||
|
|
29fdcb80f6 | ||
|
|
817ea44e8d | ||
|
|
3a1ea3df85 | ||
|
|
2667a897a1 | ||
|
|
65129604bd | ||
|
|
4e0c9b36a0 | ||
|
|
64cb9f3ff2 | ||
|
|
cb094a1f4b | ||
|
|
d05fefc231 | ||
|
|
3e1805a20f | ||
|
|
f58dfe1284 | ||
|
|
f67b7f8b66 | ||
|
|
869691d43f | ||
|
|
817d808ec5 | ||
|
|
aee1bce612 | ||
|
|
8b38a9c4e0 | ||
|
|
0a115fab17 | ||
|
|
882147b533 | ||
|
|
eb06aef199 | ||
|
|
1b91e7ce30 | ||
|
|
535e670c63 | ||
|
|
0b4c77155e | ||
|
|
e36722ccb4 | ||
|
|
ec0b94a920 | ||
|
|
d5eed8b15f | ||
|
|
2447afc43e | ||
|
|
8eef757f7f | ||
|
|
b1c29b3bf1 | ||
|
|
b211c9f150 | ||
|
|
66cae715f4 | ||
|
|
5a4a2782e8 | ||
|
|
4e462ddbef | ||
|
|
48a962a750 | ||
|
|
9a55402bdf | ||
|
|
663644553e | ||
|
|
ceec3f0e65 | ||
|
|
904d1835d2 | ||
|
|
77366bf81b | ||
|
|
cd8b048829 | ||
|
|
d62596ecfc | ||
|
|
2b496c62f1 | ||
|
|
a3c77708e2 | ||
|
|
b9e8139699 | ||
|
|
c82dcabd19 | ||
|
|
26b37d5274 | ||
|
|
d5c464b26b | ||
|
|
c48d4c5425 | ||
|
|
b28cd9c8b0 | ||
|
|
071017879c | ||
|
|
5535c83db5 | ||
|
|
8f9eccabaf | ||
|
|
322cdaf888 | ||
|
|
edcdbfd8f5 | ||
|
|
d36f13d7e2 | ||
|
|
8548b33bd9 | ||
|
|
0e148bb1b3 | ||
|
|
9ddb37a982 | ||
|
|
63227cd2c5 | ||
|
|
dc38dc1c10 | ||
|
|
2c6757ff62 | ||
|
|
0d03b09ae9 | ||
|
|
566da07448 | ||
|
|
31a3840b1f |
@@ -1,3 +1,4 @@
|
||||
REACT_APP_AMPLITUDE_KEY="1c694b28cd089acc2c386d518f93a775"
|
||||
REACT_APP_INFURA_KEY="099fc58e0de9451d80b18d7c74caa7c1"
|
||||
REACT_APP_FORTMATIC_KEY="pk_live_F937DF033A1666BF"
|
||||
REACT_APP_GOOGLE_ANALYTICS_ID="G-KDP9B6W4H8"
|
||||
|
||||
21
.github/actions/setup/action.yml
vendored
Normal file
21
.github/actions/setup/action.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: Setup
|
||||
runs:
|
||||
using: composite
|
||||
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
|
||||
shell: bash
|
||||
8
.github/dependabot.yml
vendored
8
.github/dependabot.yml
vendored
@@ -2,9 +2,9 @@ version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
# Files stored in repository root
|
||||
directory: "/"
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: "daily"
|
||||
interval: 'daily'
|
||||
allow:
|
||||
- dependency-name: "@uniswap/token-lists"
|
||||
- dependency-name: "@uniswap/default-token-list"
|
||||
- dependency-name: '@uniswap/token-lists'
|
||||
- dependency-name: '@uniswap/default-token-list'
|
||||
|
||||
6
.github/workflows/check-pr-title.yaml
vendored
6
.github/workflows/check-pr-title.yaml
vendored
@@ -1,4 +1,4 @@
|
||||
name: "Check PR Title"
|
||||
name: Check PR Title
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
@@ -8,8 +8,8 @@ on:
|
||||
- synchronize
|
||||
|
||||
jobs:
|
||||
check-pr-title:
|
||||
name: Check PR Title
|
||||
# Ensures that the PR title adheres to [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/).
|
||||
conventional-commit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v3.4.0
|
||||
|
||||
31
.github/workflows/crowdin-sync.yaml
vendored
31
.github/workflows/crowdin-sync.yaml
vendored
@@ -2,34 +2,18 @@ name: Crowdin Download
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 * * * *' # every hour we download translations and update the pr from crowdin
|
||||
|
||||
# Download translations every hour.
|
||||
# This is not done as part of the build so that builds remain reproducible.
|
||||
- cron: '0 * * * *'
|
||||
# manual trigger
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
download-translations:
|
||||
name: Download translations
|
||||
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
|
||||
|
||||
- uses: ./.github/actions/setup
|
||||
- run: yarn i18n:extract
|
||||
|
||||
- name: Download Crowdin translations
|
||||
@@ -41,8 +25,9 @@ jobs:
|
||||
token: ${{ secrets.CROWDIN_PERSONAL_TOKEN_SECRET }}
|
||||
source: 'src/locales/en-US.po'
|
||||
translation: 'src/locales/%locale%.po'
|
||||
create_pull_request: false
|
||||
localization_branch_name: main
|
||||
commit_message: "chore(i18n): synchronize translations from crowdin [skip ci]"
|
||||
create_pull_request: true
|
||||
pull_request_title: 'chore(i18n): new Crowdin translations'
|
||||
localization_branch_name: l10n_crowdin
|
||||
commit_message: 'chore(i18n): synchronize translations from crowdin [skip ci]'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
21
.github/workflows/crowdin.yaml
vendored
21
.github/workflows/crowdin.yaml
vendored
@@ -6,28 +6,11 @@ on:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
synchronize-with-crowdin:
|
||||
name: Upload sources to Crowdin
|
||||
upload-sources:
|
||||
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
|
||||
|
||||
- uses: ./.github/actions/setup
|
||||
- run: yarn i18n:extract
|
||||
|
||||
- name: Upload Crowdin sources
|
||||
|
||||
45
.github/workflows/lint.yml
vendored
45
.github/workflows/lint.yml
vendored
@@ -1,45 +0,0 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
run-linters:
|
||||
name: Run linters
|
||||
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
|
||||
|
||||
- name: Run eslint w/ autofix
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.owner.login == github.repository_owner }}
|
||||
uses: wearerequired/lint-action@36c7e6689e80d785d27a22f71d970f3a3b4fcb70
|
||||
with:
|
||||
github_token: ${{ secrets.github_token }}
|
||||
eslint: true
|
||||
eslint_args: "-c .eslintrc.json"
|
||||
auto_fix: true
|
||||
|
||||
- name: Run eslint
|
||||
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.owner.login != github.repository_owner }}
|
||||
run: yarn eslint .
|
||||
104
.github/workflows/release.yaml
vendored
104
.github/workflows/release.yaml
vendored
@@ -1,61 +1,58 @@
|
||||
name: Release
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 12 * * 1-4' # every day 12:00 UTC Monday-Thursday
|
||||
|
||||
- cron: '0 12 * * 1-4' # every day 12:00 UTC Monday-Thursday
|
||||
# manual trigger
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
bump_version:
|
||||
name: Bump Version
|
||||
wait-on-tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- id: unit-tests
|
||||
uses: fountainhead/action-wait-for-check@v1.0.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
checkName: unit-tests
|
||||
- id: cypress-tests
|
||||
uses: fountainhead/action-wait-for-check@v1.0.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
checkName: cypress-tests
|
||||
- if: steps.unit-tests.outputs.conclusion != 'success' || steps.cypress-tests.outputs.conclusion != 'success'
|
||||
run: exit 1
|
||||
|
||||
tag:
|
||||
needs: wait-on-tests
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
new_tag: ${{ steps.github_tag_action.outputs.new_tag }}
|
||||
changelog: ${{ steps.github_tag_action.outputs.changelog }}
|
||||
new_tag: ${{ steps.github-tag-action.outputs.new_tag }}
|
||||
changelog: ${{ steps.github-tag-action.outputs.changelog }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Bump version and push tag
|
||||
id: github_tag_action
|
||||
uses: mathieudutour/github-tag-action@331898d5052eedac9b15fec867b5ba66ebf9b692
|
||||
- name: Bump and tag
|
||||
id: github-tag-action
|
||||
uses: mathieudutour/github-tag-action@v6.0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
release_branches: .*
|
||||
default_bump: false
|
||||
default_bump: patch
|
||||
|
||||
create_release:
|
||||
name: Create Release
|
||||
release:
|
||||
needs: tag
|
||||
if: ${{ needs.tag.outputs.new_tag != null }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: bump_version
|
||||
if: ${{ needs.bump_version.outputs.new_tag != null }}
|
||||
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
|
||||
|
||||
- uses: ./.github/actions/setup
|
||||
- run: yarn prepare
|
||||
|
||||
- run: yarn build
|
||||
|
||||
- name: Pin to IPFS
|
||||
id: upload
|
||||
id: pinata
|
||||
uses: anantaramdas/ipfs-pinata-deploy-action@39bbda1ce1fe24c69c6f57861b8038278d53688d
|
||||
with:
|
||||
pin-name: Uniswap ${{ needs.bump_version.outputs.new_tag }}
|
||||
pin-name: Uniswap ${{ needs.tag.outputs.new_tag }}
|
||||
path: './build'
|
||||
pinata-api-key: ${{ secrets.PINATA_API_KEY }}
|
||||
pinata-secret-api-key: ${{ secrets.PINATA_API_SECRET_KEY }}
|
||||
@@ -69,10 +66,24 @@ jobs:
|
||||
seeds: ${{ secrets.CRUST_SEEDS }}
|
||||
|
||||
- name: Convert CIDv0 to CIDv1
|
||||
id: convert_cidv0
|
||||
id: convert-cidv0
|
||||
uses: uniswap/convert-cidv0-cidv1@v1.0.0
|
||||
with:
|
||||
cidv0: ${{ steps.upload.outputs.hash }}
|
||||
cidv0: ${{ steps.pinata.outputs.hash }}
|
||||
|
||||
- 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
|
||||
browser: chrome
|
||||
config-file: cypress.release.config.ts
|
||||
config: baseUrl=https://cloudflare-ipfs.com/ipfs/${{ steps.pinata.outputs.hash }}
|
||||
|
||||
- name: Update DNS with new IPFS hash
|
||||
env:
|
||||
@@ -82,20 +93,19 @@ jobs:
|
||||
CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }}
|
||||
uses: textileio/cloudflare-update-dnslink@0fe7b7a1ffc865db3a4da9773f0f987447ad5848
|
||||
with:
|
||||
cid: ${{ steps.upload.outputs.hash }}
|
||||
cid: ${{ steps.pinata.outputs.hash }}
|
||||
|
||||
- name: Create GitHub Release
|
||||
id: create_release
|
||||
- name: Release
|
||||
uses: actions/create-release@v1.1.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ needs.bump_version.outputs.new_tag }}
|
||||
release_name: Release ${{ needs.bump_version.outputs.new_tag }}
|
||||
tag_name: ${{ needs.tag.outputs.new_tag }}
|
||||
release_name: Release ${{ needs.tag.outputs.new_tag }}
|
||||
body: |
|
||||
IPFS hash of the deployment:
|
||||
- CIDv0: `${{ steps.upload.outputs.hash }}`
|
||||
- CIDv1: `${{ steps.convert_cidv0.outputs.cidv1 }}`
|
||||
- CIDv0: `${{ steps.pinata.outputs.hash }}`
|
||||
- CIDv1: `${{ steps.convert-cidv0.outputs.cidv1 }}`
|
||||
|
||||
The latest release is always accessible via our alias to the Cloudflare IPFS gateway at [app.uniswap.org](https://app.uniswap.org).
|
||||
|
||||
@@ -105,8 +115,8 @@ jobs:
|
||||
Your Uniswap settings are never remembered across different URLs.
|
||||
|
||||
IPFS gateways:
|
||||
- https://${{ steps.convert_cidv0.outputs.cidv1 }}.ipfs.dweb.link/
|
||||
- https://${{ steps.convert_cidv0.outputs.cidv1 }}.ipfs.cf-ipfs.com/
|
||||
- [ipfs://${{ steps.upload.outputs.hash }}/](ipfs://${{ steps.upload.outputs.hash }}/)
|
||||
- https://${{ steps.convert-cidv0.outputs.cidv1 }}.ipfs.dweb.link/
|
||||
- https://${{ steps.convert-cidv0.outputs.cidv1 }}.ipfs.cf-ipfs.com/
|
||||
- [ipfs://${{ steps.upload.outputs.hash }}/](ipfs://${{ steps.pinata.outputs.hash }}/)
|
||||
|
||||
${{ needs.bump_version.outputs.changelog }}
|
||||
${{ needs.tag.outputs.changelog }}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: End-to-End Tests
|
||||
name: Test
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -7,33 +7,44 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
# manual trigger
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- run: yarn lint
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 14
|
||||
registry-url: https://registry.npmjs.org
|
||||
cache: 'yarn'
|
||||
deps-tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
# Test that deps satisfy both "fewer" and "highest" strategies. This is to ensure we are
|
||||
# up-to-date ("highest") while avoiding increasing package size ("fewer").
|
||||
# These are readonly (--list) and explicitly exclude packages which only satisfy one strategy.
|
||||
- run: npx yarn-deduplicate --strategy=fewer --list --fail --exclude commander safe-buffer
|
||||
- run: npx yarn-deduplicate --strategy=highest --list --fail --exclude commander safe-buffer
|
||||
|
||||
- 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
|
||||
unit-tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- run: yarn prepare
|
||||
- run: yarn test
|
||||
|
||||
cypress-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- run: yarn prepare
|
||||
|
||||
- run: yarn build
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: build
|
||||
@@ -45,35 +56,19 @@ jobs:
|
||||
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
|
||||
cypress-test-matrix:
|
||||
needs: cypress-build
|
||||
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: ./.github/actions/setup
|
||||
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
@@ -85,7 +80,6 @@ jobs:
|
||||
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
|
||||
|
||||
@@ -98,6 +92,12 @@ jobs:
|
||||
record: true
|
||||
parallel: true
|
||||
env:
|
||||
CI: false # disables lint checks when building
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Included as a single job to check against for cypress test success, as cypress runs in a matrix.
|
||||
cypress-tests:
|
||||
needs: cypress-test-matrix
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo 'Finished cypress tests https\://dashboard.cypress.io/projects/yp82ef'
|
||||
35
.github/workflows/tests-unit.yaml
vendored
35
.github/workflows/tests-unit.yaml
vendored
@@ -1,35 +0,0 @@
|
||||
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
|
||||
@@ -48,4 +48,4 @@ The Uniswap Interface supports swapping, adding liquidity, removing liquidity an
|
||||
## Accessing Uniswap V1
|
||||
|
||||
The Uniswap V1 interface for mainnet and testnets is accessible via IPFS gateways
|
||||
linked from the [v1.0.0 release](https://github.com/Uniswap/uniswap-interface/releases/tag/v1.0.0).
|
||||
linked from the [v1.0.0 release](https://github.com/Uniswap/uniswap-interface/releases/tag/v1.0.0).
|
||||
|
||||
8
cypress.release.config.ts
Normal file
8
cypress.release.config.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { defineConfig } from 'cypress'
|
||||
|
||||
export default defineConfig({
|
||||
projectId: 'yp82ef',
|
||||
e2e: {
|
||||
specPattern: 'cypress/release.ts',
|
||||
},
|
||||
})
|
||||
8
cypress/e2e/link.test.ts
Normal file
8
cypress/e2e/link.test.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// see https://github.com/Uniswap/interface/pull/4115
|
||||
describe('Link', () => {
|
||||
it('should update route', () => {
|
||||
cy.visit('/')
|
||||
cy.get('[data-cy="pool-nav-link"]').click()
|
||||
cy.get('[data-cy="join-pool-button"]').should('exist')
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
describe('Swap', () => {
|
||||
beforeEach(() => {
|
||||
before(() => {
|
||||
cy.visit('/swap')
|
||||
})
|
||||
|
||||
@@ -11,73 +11,43 @@ describe('Swap', () => {
|
||||
})
|
||||
|
||||
it('can enter an amount into input', () => {
|
||||
cy.get('#swap-currency-input .token-amount-input')
|
||||
.clear()
|
||||
.type('0.001', { delay: 200 })
|
||||
.should('have.value', '0.001')
|
||||
cy.get('#swap-currency-input .token-amount-input').clear().type('0.001').should('have.value', '0.001')
|
||||
})
|
||||
|
||||
it('zero swap amount', () => {
|
||||
cy.get('#swap-currency-input .token-amount-input').clear().type('0.0', { delay: 200 }).should('have.value', '0.0')
|
||||
cy.get('#swap-currency-input .token-amount-input').clear().type('0.0').should('have.value', '0.0')
|
||||
})
|
||||
|
||||
it('invalid swap amount', () => {
|
||||
cy.get('#swap-currency-input .token-amount-input').clear().type('\\', { delay: 200 }).should('have.value', '')
|
||||
cy.get('#swap-currency-input .token-amount-input').clear().type('\\').should('have.value', '')
|
||||
})
|
||||
|
||||
it('can enter an amount into output', () => {
|
||||
cy.get('#swap-currency-output .token-amount-input').type('0.001', { delay: 200 }).should('have.value', '0.001')
|
||||
cy.get('#swap-currency-output .token-amount-input').clear().type('0.001').should('have.value', '0.001')
|
||||
})
|
||||
|
||||
it('zero output amount', () => {
|
||||
cy.get('#swap-currency-output .token-amount-input').type('0.0', { delay: 200 }).should('have.value', '0.0')
|
||||
cy.get('#swap-currency-output .token-amount-input').clear().type('0.0').should('have.value', '0.0')
|
||||
})
|
||||
|
||||
it.skip('can swap ETH for DAI', () => {
|
||||
cy.get('#swap-currency-output .open-currency-select-button').click()
|
||||
cy.get('.token-item-0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735').should('be.visible')
|
||||
cy.get('.token-item-0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735').click({ force: true })
|
||||
cy.get('#swap-currency-input .token-amount-input').should('be.visible')
|
||||
cy.get('#swap-currency-input .token-amount-input').type('0.001', { force: true, delay: 200 })
|
||||
cy.get('.token-item-0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735').click()
|
||||
cy.get('#swap-currency-input .token-amount-input').clear().type('0.0000001')
|
||||
cy.get('#swap-currency-output .token-amount-input').should('not.equal', '')
|
||||
cy.get('#swap-button').click()
|
||||
cy.get('#confirm-swap-or-send').should('contain', 'Confirm Swap')
|
||||
cy.get('[data-cy="confirmation-close-icon"]').click()
|
||||
})
|
||||
|
||||
it.skip('add a recipient does not exist unless in expert mode', () => {
|
||||
it('add a recipient does not exist unless in expert mode', () => {
|
||||
cy.get('#add-recipient-button').should('not.exist')
|
||||
})
|
||||
|
||||
it('ETH to wETH is same value (wrapped swaps have no price impact)', () => {
|
||||
it.skip('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('.token-item-0xc778417E063141139Fce010982780140Aa0cD5Ab').click()
|
||||
cy.get('#swap-currency-input .token-amount-input').clear().type('0.01')
|
||||
cy.get('#swap-currency-output .token-amount-input').should('have.value', '0.01')
|
||||
})
|
||||
|
||||
describe('expert mode', () => {
|
||||
beforeEach(() => {
|
||||
cy.window().then((win) => {
|
||||
cy.stub(win, 'prompt').returns('confirm')
|
||||
})
|
||||
cy.get('#open-settings-dialog-button').click()
|
||||
cy.get('#toggle-expert-mode-button').click()
|
||||
cy.get('#confirm-expert-mode').click()
|
||||
})
|
||||
|
||||
it.skip('add a recipient is visible', () => {
|
||||
cy.get('#add-recipient-button').should('be.visible')
|
||||
})
|
||||
|
||||
it.skip('add a recipient', () => {
|
||||
cy.get('#add-recipient-button').click()
|
||||
cy.get('#recipient').should('exist')
|
||||
})
|
||||
|
||||
it.skip('remove recipient', () => {
|
||||
cy.get('#add-recipient-button').click()
|
||||
cy.get('#remove-recipient-button').click()
|
||||
cy.get('#recipient').should('not.exist')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,25 +6,25 @@ describe('Wallet', () => {
|
||||
})
|
||||
|
||||
it('displays account details', () => {
|
||||
cy.get('#web3-status-connected').contains(TEST_ADDRESS_NEVER_USE_SHORTENED).click()
|
||||
cy.get('[data-testid=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)
|
||||
cy.get('[data-testid=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')
|
||||
cy.contains('Change').click()
|
||||
cy.get('[data-testid=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)
|
||||
cy.get('[data-testid=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')
|
||||
cy.contains('Disconnect').click()
|
||||
cy.get('[data-testid=option-grid]').should('exist')
|
||||
})
|
||||
})
|
||||
|
||||
20
cypress/release.ts
Normal file
20
cypress/release.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
const ONE_MINUTE = 60_000
|
||||
|
||||
describe(
|
||||
'Release',
|
||||
{
|
||||
pageLoadTimeout: ONE_MINUTE,
|
||||
retries: 30,
|
||||
},
|
||||
() => {
|
||||
it('loads swap page', () => {
|
||||
// We *must* wait in order to space out the retry attempts.
|
||||
cy.wait(ONE_MINUTE)
|
||||
.visit('/', {
|
||||
retryOnStatusCodeFailure: true,
|
||||
retryOnNetworkFailure: true,
|
||||
})
|
||||
.get('#swap-page')
|
||||
})
|
||||
}
|
||||
)
|
||||
@@ -50,3 +50,9 @@ beforeEach(() => {
|
||||
res.continue()
|
||||
})
|
||||
})
|
||||
|
||||
Cypress.on('uncaught:exception', (_err, _runnable) => {
|
||||
// returning false here prevents Cypress from
|
||||
// failing the test
|
||||
return false
|
||||
})
|
||||
|
||||
145
package.json
145
package.json
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@uniswap/widgets",
|
||||
"name": "@uniswap/interface",
|
||||
"version": "1.0.7",
|
||||
"description": "Uniswap Interface",
|
||||
"homepage": ".",
|
||||
@@ -9,7 +9,7 @@
|
||||
"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",
|
||||
"prei18n:extract": "node prei18n-extract.js",
|
||||
"i18n:extract": "lingui extract --locale en-US",
|
||||
"i18n:compile": "yarn i18n:extract && lingui compile",
|
||||
"i18n:pseudo": "lingui extract --locale pseudo && lingui compile",
|
||||
@@ -17,6 +17,7 @@
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"serve": "serve build -l 3000",
|
||||
"lint": "yarn eslint .",
|
||||
"test": "react-scripts test --coverage",
|
||||
"cypress:open": "cypress open --browser chrome --e2e",
|
||||
"cypress:run": "cypress run --browser chrome --e2e"
|
||||
@@ -53,29 +54,15 @@
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@coinbase/wallet-sdk": "^3.2.0",
|
||||
"devDependencies": {
|
||||
"@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",
|
||||
"@graphql-codegen/typescript-operations": "^1.18.2",
|
||||
"@graphql-codegen/typescript-rtk-query": "^1.1.1",
|
||||
"@lingui/cli": "^3.9.0",
|
||||
"@lingui/core": "^3.9.0",
|
||||
"@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",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^13.1",
|
||||
"@typechain/ethers-v5": "^7.0.0",
|
||||
"@types/array.prototype.flat": "^1.2.1",
|
||||
"@types/array.prototype.flatmap": "^1.2.2",
|
||||
@@ -88,52 +75,20 @@
|
||||
"@types/multicodec": "^1.0.0",
|
||||
"@types/node": "^13.13.5",
|
||||
"@types/qs": "^6.9.2",
|
||||
"@types/react": "^17.0.2",
|
||||
"@types/react-dom": "^17.0.1",
|
||||
"@types/react-redux": "^7.1.16",
|
||||
"@types/react-router-dom": "^5.0.0",
|
||||
"@types/react": "^18.0.15",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react-redux": "^7.1.24",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.0",
|
||||
"@types/react-window": "^1.8.2",
|
||||
"@types/rebass": "^4.0.7",
|
||||
"@types/styled-components": "^5.1.0",
|
||||
"@types/styled-components": "^5.1.25",
|
||||
"@types/testing-library__cypress": "^5.0.5",
|
||||
"@types/ua-parser-js": "^0.7.35",
|
||||
"@types/wcag-contrast": "^3.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.1.0",
|
||||
"@typescript-eslint/parser": "^4.1.0",
|
||||
"@uniswap/default-token-list": "^3.0.0",
|
||||
"@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",
|
||||
"@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",
|
||||
"cids": "^1.0.0",
|
||||
"copy-to-clipboard": "^3.2.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4",
|
||||
"@typescript-eslint/parser": "^4",
|
||||
"cypress": "^10.1.0",
|
||||
"d3": "^7.0.0",
|
||||
"env-cmd": "^10.1.0",
|
||||
"eslint": "^7.11.0",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
@@ -143,6 +98,59 @@
|
||||
"eslint-plugin-react-hooks": "^4.0.0",
|
||||
"eslint-plugin-simple-import-sort": "^7.0.0",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"jest-styled-components": "7.0.7",
|
||||
"ms.macro": "^2.0.0",
|
||||
"prettier": "^2.7.1",
|
||||
"react-scripts": "^4.0.3",
|
||||
"serve": "^11.3.2",
|
||||
"typechain": "^5.0.0",
|
||||
"typescript": "^4.4.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@amplitude/analytics-browser": "^0.5.1",
|
||||
"@coinbase/wallet-sdk": "^3.3.0",
|
||||
"@fontsource/ibm-plex-mono": "^4.5.1",
|
||||
"@fontsource/inter": "^4.5.1",
|
||||
"@lingui/core": "^3.14.0",
|
||||
"@lingui/macro": "^3.14.0",
|
||||
"@lingui/react": "^3.14.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",
|
||||
"@uniswap/governance": "^1.0.2",
|
||||
"@uniswap/liquidity-staker": "^1.0.2",
|
||||
"@uniswap/merkle-distributor": "1.0.1",
|
||||
"@uniswap/redux-multicall": "^1.1.5",
|
||||
"@uniswap/router-sdk": "^1.0.6",
|
||||
"@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",
|
||||
"@uniswap/v3-sdk": "^3.9.0",
|
||||
"@walletconnect/ethereum-provider": "1.7.1",
|
||||
"@web3-react/coinbase-wallet": "^8.0.34-beta.0",
|
||||
"@web3-react/core": "^8.0.35-beta.0",
|
||||
"@web3-react/eip1193": "^8.0.26-beta.0",
|
||||
"@web3-react/empty": "^8.0.20-beta.0",
|
||||
"@web3-react/gnosis-safe": "^8.0.6-beta.0",
|
||||
"@web3-react/metamask": "^8.0.28-beta.0",
|
||||
"@web3-react/network": "^8.0.27-beta.0",
|
||||
"@web3-react/types": "^8.0.20-beta.0",
|
||||
"@web3-react/url": "^8.0.25-beta.0",
|
||||
"@web3-react/walletconnect": "^8.0.35-beta.0",
|
||||
"ajv": "^6.12.3",
|
||||
"array.prototype.flat": "^1.2.4",
|
||||
"array.prototype.flatmap": "^1.2.4",
|
||||
"cids": "^1.0.0",
|
||||
"copy-to-clipboard": "^3.2.0",
|
||||
"d3": "^7.0.0",
|
||||
"ethers": "^5.1.4",
|
||||
"firebase": "^9.1.3",
|
||||
"fortmatic": "^2.4.0",
|
||||
@@ -150,30 +158,26 @@
|
||||
"graphql-request": "^3.4.0",
|
||||
"immer": "^9.0.6",
|
||||
"inter-ui": "^3.13.1",
|
||||
"jest-styled-components": "^7.0.5",
|
||||
"jotai": "^1.3.7",
|
||||
"jsbi": "^3.1.4",
|
||||
"make-plural": "^7.0.0",
|
||||
"ms.macro": "^2.0.0",
|
||||
"multicodec": "^3.0.1",
|
||||
"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": "^18.2.0",
|
||||
"react-confetti": "^6.0.0",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"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-redux": "^8.0.2",
|
||||
"react-router-dom": "^5.3.3",
|
||||
"react-spring": "^8.0.27",
|
||||
"react-use-gesture": "^6.0.14",
|
||||
"react-virtualized-auto-sizer": "^1.0.2",
|
||||
@@ -181,13 +185,9 @@
|
||||
"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",
|
||||
"styled-components": "^5.3.5",
|
||||
"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",
|
||||
@@ -197,8 +197,5 @@
|
||||
"workbox-navigation-preload": "^6.1.0",
|
||||
"workbox-precaching": "^6.1.0",
|
||||
"workbox-routing": "^6.1.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"@walletconnect/ethereum-provider": "1.7.1"
|
||||
}
|
||||
}
|
||||
|
||||
9
prei18n-extract.js
Normal file
9
prei18n-extract.js
Normal file
@@ -0,0 +1,9 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const exec = require('child_process').exec
|
||||
const isWindows = process.platform === 'win32' || /^(msys|cygwin)$/.test(process.env.OSTYPE)
|
||||
|
||||
if (isWindows) {
|
||||
exec(`type nul > src/locales/en-US.po`)
|
||||
} else {
|
||||
exec(`touch src/locales/en-US.po`)
|
||||
}
|
||||
@@ -24,83 +24,83 @@
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
|
||||
<link rel="preconnect" href="https://www.google-analytics.com/">
|
||||
<link rel="preconnect" href="https://www.google-analytics.com/" />
|
||||
|
||||
<link rel="preload" href="%PUBLIC_URL%/fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="%PUBLIC_URL%/fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
* {
|
||||
font-family: 'Inter', sans-serif;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
* {
|
||||
font-family: 'Inter', sans-serif;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
Explicitly load Inter var from public/ so it does not block LCP's critical path.
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Inter custom';
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Regular';
|
||||
src: url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format("woff2 supports variations(gvar)"),
|
||||
url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format("woff2-variations"),
|
||||
url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format("woff2");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter custom';
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Regular';
|
||||
src: url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
|
||||
url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format('woff2-variations'),
|
||||
url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format('woff2');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Inter custom', sans-serif;
|
||||
}
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Inter custom', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
user-select: none;
|
||||
}
|
||||
button {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
font-variant: none;
|
||||
font-smooth: always;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
font-feature-settings: 'ss01' on, 'ss02' on, 'cv01' on, 'cv03' on;
|
||||
}
|
||||
|
||||
#background-radial-gradient {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
width: 200vw;
|
||||
height: 200vh;
|
||||
background: radial-gradient(50% 50% at 50% 50%, #fc077d10 0%, rgba(255, 255, 255, 0) 100%);
|
||||
transform: translate(-50vw, -100vh);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
html {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
font-size: 16px;
|
||||
font-variant: none;
|
||||
font-smooth: always;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
font-feature-settings: 'ss01' on, 'ss02' on, 'cv01' on, 'cv03' on;
|
||||
background-color: #212429;
|
||||
}
|
||||
|
||||
#background-radial-gradient {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
width: 200vw;
|
||||
height: 200vh;
|
||||
background: radial-gradient(50% 50% at 50% 50%, #fc077d10 0%, rgba(255, 255, 255, 0) 100%);
|
||||
transform: translate(-50vw, -100vh);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
}
|
||||
@media (prefers-color-scheme: light) {
|
||||
html {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background-color: #212429;
|
||||
}
|
||||
}
|
||||
@media (prefers-color-scheme: light) {
|
||||
html {
|
||||
background-color: #F7F8FA;
|
||||
}
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB |
1
src/assets/svg/celo_logo.svg
Normal file
1
src/assets/svg/celo_logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Celo_Rings" data-name="Celo Rings" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 950 950"><defs><style>.cls-1{fill:#fbcc5c;}.cls-2{fill:#35d07f;}.cls-3{fill:#5ea33b;}</style></defs><title>Artboard 1</title><path id="Bottom_Ring" data-name="Bottom Ring" class="cls-1" d="M375,850c151.88,0,275-123.12,275-275S526.88,300,375,300,100,423.12,100,575,223.12,850,375,850Zm0,100C167.9,950,0,782.1,0,575S167.9,200,375,200,750,367.9,750,575,582.1,950,375,950Z"/><path id="Top_Ring" data-name="Top Ring" class="cls-2" d="M575,650c151.88,0,275-123.12,275-275S726.88,100,575,100,300,223.12,300,375,423.12,650,575,650Zm0,100c-207.1,0-375-167.9-375-375S367.9,0,575,0,950,167.9,950,375,782.1,750,575,750Z"/><path id="Rings_Overlap" data-name="Rings Overlap" class="cls-3" d="M587.39,750a274.38,274.38,0,0,0,54.55-108.06A274.36,274.36,0,0,0,750,587.4a373.63,373.63,0,0,1-29.16,133.45A373.62,373.62,0,0,1,587.39,750ZM308.06,308.06A274.36,274.36,0,0,0,200,362.6a373.63,373.63,0,0,1,29.16-133.45A373.62,373.62,0,0,1,362.61,200,274.38,274.38,0,0,0,308.06,308.06Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -1,4 +1,4 @@
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { CheckCircle, Triangle } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
@@ -35,7 +35,7 @@ const IconWrapper = styled.div<{ pending: boolean; success?: boolean }>`
|
||||
`
|
||||
|
||||
export default function Transaction({ hash }: { hash: string }) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId } = useWeb3React()
|
||||
const allTransactions = useAllTransactions()
|
||||
|
||||
const tx = allTransactions?.[hash]
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Connector } from '@web3-react/types'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import CopyHelper from 'components/AccountDetails/Copy'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useCallback, useContext } from 'react'
|
||||
import { getConnection, getConnectionName, getIsCoinbaseWallet, getIsMetaMask } from 'connection/utils'
|
||||
import { Context, useCallback, useContext } from 'react'
|
||||
import { ExternalLink as LinkIcon } from 'react-feather'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
import { updateSelectedWallet } from 'state/user/reducer'
|
||||
import { DefaultTheme } from 'styled-components/macro'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
import { isMobile } from 'utils/userAgent'
|
||||
|
||||
import { ReactComponent as Close } from '../../assets/images/x.svg'
|
||||
import { coinbaseWallet, injected } from '../../connectors'
|
||||
import { SUPPORTED_WALLETS } from '../../constants/wallet'
|
||||
import { clearAllTransactions } from '../../state/transactions/reducer'
|
||||
import { ExternalLink, LinkStyledButton, ThemedText } from '../../theme'
|
||||
import { shortenAddress } from '../../utils'
|
||||
@@ -162,29 +162,6 @@ const WalletName = styled.div`
|
||||
color: ${({ theme }) => theme.text3};
|
||||
`
|
||||
|
||||
const IconWrapper = styled.div<{ size?: number }>`
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 8px;
|
||||
& > img,
|
||||
span {
|
||||
height: ${({ size }) => (size ? size + 'px' : '32px')};
|
||||
width: ${({ size }) => (size ? size + 'px' : '32px')};
|
||||
}
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
align-items: flex-end;
|
||||
`};
|
||||
`
|
||||
|
||||
function WrappedStatusIcon({ connector }: { connector: Connector }) {
|
||||
return (
|
||||
<IconWrapper size={16}>
|
||||
<StatusIcon connector={connector} />
|
||||
</IconWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
const TransactionListWrapper = styled.div`
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
`
|
||||
@@ -226,22 +203,20 @@ export default function AccountDetails({
|
||||
ENSName,
|
||||
openOptions,
|
||||
}: AccountDetailsProps) {
|
||||
const { chainId, account, connector } = useActiveWeb3React()
|
||||
const theme = useContext(ThemeContext)
|
||||
const { chainId, account, connector } = useWeb3React()
|
||||
const connectionType = getConnection(connector).type
|
||||
|
||||
const theme = useContext(ThemeContext as Context<DefaultTheme>)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const isMetaMask = getIsMetaMask()
|
||||
const isCoinbaseWallet = getIsCoinbaseWallet()
|
||||
const isInjectedMobileBrowser = (isMetaMask || isCoinbaseWallet) && isMobile
|
||||
|
||||
function formatConnectorName() {
|
||||
const { ethereum } = window
|
||||
const isMetaMask = !!(ethereum && ethereum.isMetaMask)
|
||||
const name = Object.keys(SUPPORTED_WALLETS)
|
||||
.filter(
|
||||
(k) =>
|
||||
SUPPORTED_WALLETS[k].connector === connector && (connector !== injected || isMetaMask === (k === 'METAMASK'))
|
||||
)
|
||||
.map((k) => SUPPORTED_WALLETS[k].name)[0]
|
||||
return (
|
||||
<WalletName>
|
||||
<Trans>Connected with {name}</Trans>
|
||||
<Trans>Connected with</Trans> {getConnectionName(connectionType, isMetaMask)}
|
||||
</WalletName>
|
||||
)
|
||||
}
|
||||
@@ -265,48 +240,41 @@ export default function AccountDetails({
|
||||
<AccountGroupingRow>
|
||||
{formatConnectorName()}
|
||||
<div>
|
||||
{/* 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.deactivate ? connector.deactivate() : connector.resetState()
|
||||
dispatch(updateSelectedWallet({ wallet: undefined }))
|
||||
openOptions()
|
||||
}}
|
||||
data-cy="wallet-disconnect"
|
||||
>
|
||||
<Trans>Disconnect</Trans>
|
||||
</WalletAction>
|
||||
{!isInjectedMobileBrowser && (
|
||||
<>
|
||||
<WalletAction
|
||||
style={{ fontSize: '.825rem', fontWeight: 400, marginRight: '8px' }}
|
||||
onClick={() => {
|
||||
if (connector.deactivate) {
|
||||
connector.deactivate()
|
||||
} else {
|
||||
connector.resetState()
|
||||
}
|
||||
|
||||
dispatch(updateSelectedWallet({ wallet: undefined }))
|
||||
openOptions()
|
||||
}}
|
||||
>
|
||||
<Trans>Disconnect</Trans>
|
||||
</WalletAction>
|
||||
<WalletAction
|
||||
style={{ fontSize: '.825rem', fontWeight: 400 }}
|
||||
onClick={() => {
|
||||
openOptions()
|
||||
}}
|
||||
>
|
||||
<Trans>Change</Trans>
|
||||
</WalletAction>
|
||||
</>
|
||||
)}
|
||||
<WalletAction
|
||||
style={{ fontSize: '.825rem', fontWeight: 400 }}
|
||||
onClick={() => {
|
||||
openOptions()
|
||||
}}
|
||||
data-cy="wallet-change"
|
||||
>
|
||||
<Trans>Change</Trans>
|
||||
</WalletAction>
|
||||
</div>
|
||||
</AccountGroupingRow>
|
||||
<AccountGroupingRow id="web3-account-identifier-row">
|
||||
<AccountGroupingRow data-testid="web3-account-identifier-row">
|
||||
<AccountControl>
|
||||
{ENSName ? (
|
||||
<>
|
||||
<div>
|
||||
{connector && <WrappedStatusIcon connector={connector} />}
|
||||
<p> {ENSName}</p>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div>
|
||||
{connector && <WrappedStatusIcon connector={connector} />}
|
||||
<p> {account && shortenAddress(account)}</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div>
|
||||
<StatusIcon connectionType={connectionType} />
|
||||
<p>{ENSName ? ENSName : account && shortenAddress(account)}</p>
|
||||
</div>
|
||||
</AccountControl>
|
||||
</AccountGroupingRow>
|
||||
<AccountGroupingRow>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t } from '@lingui/macro'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { ReactNode, useCallback, useContext } from 'react'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ChangeEvent, Context, ReactNode, useCallback, useContext } from 'react'
|
||||
import styled, { DefaultTheme, ThemeContext } from 'styled-components/macro'
|
||||
|
||||
import useENS from '../../hooks/useENS'
|
||||
import { ExternalLink, ThemedText } from '../../theme'
|
||||
@@ -86,13 +86,13 @@ export default function AddressInputPanel({
|
||||
// triggers whenever the typed value changes
|
||||
onChange: (value: string) => void
|
||||
}) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const theme = useContext(ThemeContext)
|
||||
const { chainId } = useWeb3React()
|
||||
const theme = useContext(ThemeContext as Context<DefaultTheme>)
|
||||
|
||||
const { address, loading, name } = useENS(value)
|
||||
|
||||
const handleInput = useCallback(
|
||||
(event) => {
|
||||
(event: ChangeEvent<HTMLInputElement>) => {
|
||||
const input = event.target.value
|
||||
const withoutSpaces = input.replace(/\s+/g, '')
|
||||
onChange(withoutSpaces)
|
||||
|
||||
57
src/components/AmplitudeAnalytics/Trace.tsx
Normal file
57
src/components/AmplitudeAnalytics/Trace.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { createContext, memo, PropsWithChildren, useContext, useEffect, useMemo } from 'react'
|
||||
|
||||
import { sendAnalyticsEvent } from '.'
|
||||
import { ElementName, EventName, ModalName, PageName, SectionName } from './constants'
|
||||
|
||||
export interface ITraceContext {
|
||||
// Highest order context: eg Swap or Explore.
|
||||
page?: PageName
|
||||
|
||||
// Enclosed section name. For contexts with modals, refers to the
|
||||
// section of the page from which the user triggered the modal.
|
||||
section?: SectionName
|
||||
|
||||
modal?: ModalName
|
||||
|
||||
// Element name mostly used to identify events sources
|
||||
// Does not need to be unique given the higher order page and section.
|
||||
element?: ElementName
|
||||
}
|
||||
|
||||
export const TraceContext = createContext<ITraceContext>({})
|
||||
|
||||
type TraceProps = {
|
||||
shouldLogImpression?: boolean // whether to log impression on mount
|
||||
name?: EventName
|
||||
properties?: Record<string, unknown>
|
||||
} & ITraceContext
|
||||
|
||||
/**
|
||||
* Sends an analytics event on mount (if shouldLogImpression is set),
|
||||
* and propagates the context to child traces.
|
||||
*/
|
||||
export const Trace = memo(
|
||||
({ shouldLogImpression, name, children, page, section, element, properties }: PropsWithChildren<TraceProps>) => {
|
||||
const parentTrace = useContext(TraceContext)
|
||||
|
||||
const combinedProps = useMemo(
|
||||
() => ({
|
||||
...parentTrace,
|
||||
...Object.fromEntries(Object.entries({ page, section, element }).filter(([_, v]) => v !== undefined)),
|
||||
}),
|
||||
[element, parentTrace, page, section]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldLogImpression) {
|
||||
sendAnalyticsEvent(name ?? EventName.PAGE_VIEWED, { ...combinedProps, ...properties })
|
||||
}
|
||||
// Impressions should only be logged on mount.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
return <TraceContext.Provider value={combinedProps}>{children}</TraceContext.Provider>
|
||||
}
|
||||
)
|
||||
|
||||
Trace.displayName = 'Trace'
|
||||
70
src/components/AmplitudeAnalytics/TraceEvent.tsx
Normal file
70
src/components/AmplitudeAnalytics/TraceEvent.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { Children, cloneElement, isValidElement, memo, PropsWithChildren, SyntheticEvent } from 'react'
|
||||
|
||||
import { sendAnalyticsEvent } from '.'
|
||||
import { Event, EventName } from './constants'
|
||||
import { ITraceContext, Trace, TraceContext } from './Trace'
|
||||
|
||||
type TraceEventProps = {
|
||||
events: Event[]
|
||||
name: EventName
|
||||
properties?: Record<string, unknown>
|
||||
} & ITraceContext
|
||||
|
||||
/**
|
||||
* Analytics instrumentation component that wraps event callbacks with logging logic.
|
||||
*
|
||||
* @example
|
||||
* <TraceEvent events={[Event.onClick]} element={ElementName.SWAP_BUTTON}>
|
||||
* <Button onClick={() => console.log('clicked')}>Click me</Button>
|
||||
* </TraceEvent>
|
||||
*/
|
||||
export const TraceEvent = memo((props: PropsWithChildren<TraceEventProps>) => {
|
||||
const { name, properties, events, children, ...traceProps } = props
|
||||
|
||||
return (
|
||||
<Trace {...traceProps}>
|
||||
<TraceContext.Consumer>
|
||||
{(traceContext) =>
|
||||
Children.map(children, (child) => {
|
||||
if (!isValidElement(child)) {
|
||||
return child
|
||||
}
|
||||
|
||||
// For each child, augment event handlers defined in `events` with event tracing.
|
||||
return cloneElement(child, getEventHandlers(child, traceContext, events, name, properties))
|
||||
})
|
||||
}
|
||||
</TraceContext.Consumer>
|
||||
</Trace>
|
||||
)
|
||||
})
|
||||
|
||||
TraceEvent.displayName = 'TraceEvent'
|
||||
|
||||
/**
|
||||
* Given a set of child element and event props, returns a spreadable
|
||||
* object of the event handlers augmented with analytics logging.
|
||||
*/
|
||||
function getEventHandlers(
|
||||
child: React.ReactElement,
|
||||
traceContext: ITraceContext,
|
||||
events: Event[],
|
||||
name: EventName,
|
||||
properties?: Record<string, unknown>
|
||||
) {
|
||||
const eventHandlers: Partial<Record<Event, (e: SyntheticEvent<Element, Event>) => void>> = {}
|
||||
|
||||
for (const event of events) {
|
||||
eventHandlers[event] = (eventHandlerArgs: unknown) => {
|
||||
// call child event handler with original arguments, must be in array
|
||||
const args = Array.isArray(eventHandlerArgs) ? eventHandlerArgs : [eventHandlerArgs]
|
||||
child.props[event]?.apply(child, args)
|
||||
|
||||
// augment handler with analytics logging
|
||||
sendAnalyticsEvent(name, { ...traceContext, ...properties, action: event })
|
||||
}
|
||||
}
|
||||
|
||||
// return a spreadable event handler object
|
||||
return eventHandlers
|
||||
}
|
||||
78
src/components/AmplitudeAnalytics/constants.ts
Normal file
78
src/components/AmplitudeAnalytics/constants.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Event names that can occur in this application.
|
||||
*
|
||||
* Subject to change as new features are added and new events are defined
|
||||
* and logged.
|
||||
*/
|
||||
export enum EventName {
|
||||
CONNECT_WALLET_BUTTON_CLICKED = 'Connect Wallet Button Clicked',
|
||||
PAGE_VIEWED = 'Page Viewed',
|
||||
SWAP_SUBMITTED = 'Swap Submitted',
|
||||
TOKEN_IMPORTED = 'Token Imported',
|
||||
TOKEN_SELECTED = 'Token Selected',
|
||||
TOKEN_SELECTOR_OPENED = 'Token Selector Opened',
|
||||
WALLET_CONNECT_TXN_COMPLETED = 'Wallet Connect Transaction Completed',
|
||||
WALLET_SELECTED = 'Wallet Selected',
|
||||
// alphabetize additional event names.
|
||||
}
|
||||
|
||||
export enum WALLET_CONNECTION_RESULT {
|
||||
SUCCEEDED = 'Succeeded',
|
||||
FAILED = 'Failed',
|
||||
}
|
||||
|
||||
/**
|
||||
* Known pages in the app. Highest order context.
|
||||
*/
|
||||
export const enum PageName {
|
||||
EXPLORE_PAGE = 'explore-page',
|
||||
POOL_PAGE = 'pool-page',
|
||||
SWAP_PAGE = 'swap-page',
|
||||
VOTE_PAGE = 'vote-page',
|
||||
// alphabetize additional page names.
|
||||
}
|
||||
|
||||
/**
|
||||
* Sections. Disambiguates low-level elements that may share a name.
|
||||
* eg a `back` button in a modal will have the same `element`,
|
||||
* but a different `section`.
|
||||
*/
|
||||
export const enum SectionName {
|
||||
CURRENCY_INPUT_PANEL = 'swap-currency-input',
|
||||
CURRENCY_OUTPUT_PANEL = 'swap-currency-output',
|
||||
// alphabetize additional section names.
|
||||
}
|
||||
|
||||
/** Known modals for analytics purposes. */
|
||||
export const enum ModalName {
|
||||
SWAP = 'swap-modal',
|
||||
TOKEN_SELECTOR = 'token-selector-modal',
|
||||
// alphabetize additional modal names.
|
||||
}
|
||||
|
||||
/**
|
||||
* Known element names for analytics purposes.
|
||||
* Use to identify low-level components given a TraceContext
|
||||
*/
|
||||
export const enum ElementName {
|
||||
COMMON_BASES_CURRENCY_BUTTON = 'common-bases-currency-button',
|
||||
CONFIRM_SWAP_BUTTON = 'confirm-swap-or-send',
|
||||
CONNECT_WALLET_BUTTON = 'connect-wallet-button',
|
||||
IMPORT_TOKEN_BUTTON = 'import-token-button',
|
||||
SWAP_BUTTON = 'swap-button',
|
||||
TOKEN_SELECTOR_ROW = 'token-selector-row',
|
||||
WALLET_TYPE_OPTION = 'wallet-type-option',
|
||||
// alphabetize additional element names.
|
||||
}
|
||||
|
||||
/**
|
||||
* Known events that trigger callbacks.
|
||||
* @example
|
||||
* <TraceEvent events={[Event.onClick]} element={name}>
|
||||
*/
|
||||
export enum Event {
|
||||
onClick = 'onClick',
|
||||
onKeyPress = 'onKeyPress',
|
||||
onSelect = 'onSelect',
|
||||
// alphabetize additional events.
|
||||
}
|
||||
88
src/components/AmplitudeAnalytics/index.ts
Normal file
88
src/components/AmplitudeAnalytics/index.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { Identify, identify, init, track } from '@amplitude/analytics-browser'
|
||||
|
||||
/**
|
||||
* Initializes Amplitude with API key for project.
|
||||
*
|
||||
* Uniswap has two Amplitude projects: test and production. You must be a
|
||||
* member of the organization on Amplitude to view details.
|
||||
*/
|
||||
export function initializeAnalytics(isDevEnvironment = process.env.NODE_ENV === 'development') {
|
||||
if (isDevEnvironment) return
|
||||
|
||||
const API_KEY = process.env.REACT_APP_AMPLITUDE_KEY
|
||||
if (typeof API_KEY === 'undefined') {
|
||||
throw new Error(`REACT_APP_AMPLITUDE_KEY must be a defined environment variable`)
|
||||
}
|
||||
|
||||
init(
|
||||
API_KEY,
|
||||
/* userId= */ undefined, // User ID should be undefined to let Amplitude default to Device ID
|
||||
/* options= */ {
|
||||
// Disable tracking of private user information by Amplitude
|
||||
trackingOptions: {
|
||||
ipAddress: false,
|
||||
carrier: false,
|
||||
city: false,
|
||||
region: false,
|
||||
country: false,
|
||||
dma: false, // designated market area
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/** Sends an event to Amplitude. */
|
||||
export function sendAnalyticsEvent(eventName: string, eventProperties?: Record<string, unknown>) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log(`[amplitude(${eventName})]: ${JSON.stringify(eventProperties)}`)
|
||||
return
|
||||
}
|
||||
|
||||
track(eventName, eventProperties)
|
||||
}
|
||||
|
||||
/**
|
||||
* Class that exposes methods to mutate the User Model's properties in
|
||||
* Amplitude that represents the current session's user.
|
||||
*
|
||||
* See https://help.amplitude.com/hc/en-us/articles/115002380567-User-properties-and-event-properties
|
||||
* for details.
|
||||
*/
|
||||
class UserModel {
|
||||
constructor(private isDevEnvironment = process.env.NODE_ENV === 'development') {}
|
||||
|
||||
private log(method: string, ...parameters: unknown[]) {
|
||||
console.debug(`[amplitude(Identify)]: ${method}(${parameters})`)
|
||||
}
|
||||
|
||||
private call(mutate: (event: Identify) => Identify) {
|
||||
if (this.isDevEnvironment) {
|
||||
const log = (_: Identify, method: string) => this.log.bind(this, method)
|
||||
mutate(new Proxy(new Identify(), { get: log }))
|
||||
return
|
||||
}
|
||||
identify(mutate(new Identify()))
|
||||
}
|
||||
|
||||
set(key: string, value: string | number) {
|
||||
this.call((event) => event.set(key, value))
|
||||
}
|
||||
|
||||
setOnce(key: string, value: string | number) {
|
||||
this.call((event) => event.setOnce(key, value))
|
||||
}
|
||||
|
||||
add(key: string, value: string | number) {
|
||||
this.call((event) => event.add(key, typeof value === 'number' ? value : 0))
|
||||
}
|
||||
|
||||
postInsert(key: string, value: string | number) {
|
||||
this.call((event) => event.postInsert(key, value))
|
||||
}
|
||||
|
||||
remove(key: string, value: string | number) {
|
||||
this.call((event) => event.remove(key, value))
|
||||
}
|
||||
}
|
||||
|
||||
export const user = new UserModel()
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ReactNode, useMemo } from 'react'
|
||||
|
||||
const BLOCKED_ADDRESSES: string[] = [
|
||||
@@ -38,7 +38,7 @@ const BLOCKED_ADDRESSES: string[] = [
|
||||
]
|
||||
|
||||
export default function Blocklist({ children }: { children: ReactNode }) {
|
||||
const { account } = useActiveWeb3React()
|
||||
const { account } = useWeb3React()
|
||||
const blocked: boolean = useMemo(() => Boolean(account && BLOCKED_ADDRESSES.indexOf(account) !== -1), [account])
|
||||
if (blocked) {
|
||||
return (
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { LoadingOpacityContainer, loadingOpacityMixin } from 'components/Loader/styled'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { darken } from 'polished'
|
||||
import { ReactNode, useCallback, useState } from 'react'
|
||||
import { Lock } from 'react-feather'
|
||||
@@ -12,7 +12,7 @@ import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
||||
|
||||
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
|
||||
import useTheme from '../../hooks/useTheme'
|
||||
import { useCurrencyBalance } from '../../state/wallet/hooks'
|
||||
import { useCurrencyBalance } from '../../state/connection/hooks'
|
||||
import { ThemedText } from '../../theme'
|
||||
import { ButtonGray } from '../Button'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
@@ -103,6 +103,7 @@ const LabelRow = styled.div`
|
||||
|
||||
const FiatRow = styled(LabelRow)`
|
||||
justify-content: flex-end;
|
||||
height: 16px;
|
||||
`
|
||||
|
||||
const Aligner = styled.span`
|
||||
@@ -201,7 +202,7 @@ export default function CurrencyInputPanel({
|
||||
...rest
|
||||
}: CurrencyInputPanelProps) {
|
||||
const [modalOpen, setModalOpen] = useState(false)
|
||||
const { account } = useActiveWeb3React()
|
||||
const { account } = useWeb3React()
|
||||
const selectedCurrencyBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined)
|
||||
const theme = useTheme()
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { AlertOctagon } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink } from 'theme'
|
||||
@@ -42,7 +42,7 @@ function Wrapper({ children }: { children: React.ReactNode }) {
|
||||
* Shows a downtime warning for the network if it's relevant
|
||||
*/
|
||||
export default function DowntimeWarning() {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId } = useWeb3React()
|
||||
if (!isL2ChainId(chainId)) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import React, { ErrorInfo } from 'react'
|
||||
import React, { ErrorInfo, PropsWithChildren } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import store, { AppState } from '../../state'
|
||||
@@ -56,8 +56,8 @@ async function updateServiceWorker(): Promise<ServiceWorkerRegistration> {
|
||||
return ready.update() as unknown as Promise<ServiceWorkerRegistration>
|
||||
}
|
||||
|
||||
export default class ErrorBoundary extends React.Component<unknown, ErrorBoundaryState> {
|
||||
constructor(props: unknown) {
|
||||
export default class ErrorBoundary extends React.Component<PropsWithChildren<unknown>, ErrorBoundaryState> {
|
||||
constructor(props: PropsWithChildren<unknown>) {
|
||||
super(props)
|
||||
this.state = { error: null }
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { ButtonGray } from 'components/Button'
|
||||
import Card from 'components/Card'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { RowBetween } from 'components/Row'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useFeeTierDistribution } from 'hooks/useFeeTierDistribution'
|
||||
import { PoolState, usePools } from 'hooks/usePools'
|
||||
import usePrevious from 'hooks/usePrevious'
|
||||
@@ -59,7 +59,7 @@ export default function FeeSelector({
|
||||
currencyA?: Currency | undefined
|
||||
currencyB?: Currency | undefined
|
||||
}) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId } = useWeb3React()
|
||||
|
||||
const { isLoading, isError, largestUsageFeeTier, distributions } = useFeeTierDistribution(currencyA, currencyB)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { ALL_SUPPORTED_CHAIN_IDS, SupportedChainId } from 'constants/chains'
|
||||
import { ReactNode } from 'react'
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
export const FEE_AMOUNT_DETAIL: Record<
|
||||
FeeAmount,
|
||||
@@ -10,7 +10,14 @@ export const FEE_AMOUNT_DETAIL: Record<
|
||||
[FeeAmount.LOWEST]: {
|
||||
label: '0.01',
|
||||
description: <Trans>Best for very stable pairs.</Trans>,
|
||||
supportedChains: [SupportedChainId.MAINNET, SupportedChainId.POLYGON, SupportedChainId.POLYGON_MUMBAI],
|
||||
supportedChains: [
|
||||
SupportedChainId.MAINNET,
|
||||
SupportedChainId.POLYGON,
|
||||
SupportedChainId.POLYGON_MUMBAI,
|
||||
SupportedChainId.CELO,
|
||||
SupportedChainId.CELO_ALFAJORES,
|
||||
SupportedChainId.OPTIMISM,
|
||||
],
|
||||
},
|
||||
[FeeAmount.LOW]: {
|
||||
label: '0.05',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { CHAIN_INFO, L2ChainInfo } from 'constants/chainInfo'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { getChainInfoOrDefault, L2ChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { AlertOctagon } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
|
||||
@@ -45,8 +45,8 @@ const Wrapper = styled.div`
|
||||
`
|
||||
|
||||
export function ChainConnectivityWarning() {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const info = CHAIN_INFO[chainId ?? SupportedChainId.MAINNET]
|
||||
const { chainId } = useWeb3React()
|
||||
const info = getChainInfoOrDefault(chainId)
|
||||
const label = info?.label
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { getWalletForConnector } from 'connectors'
|
||||
import { CHAIN_INFO } from 'constants/chainInfo'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { getConnection } from 'connection/utils'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { CHAIN_IDS_TO_NAMES, SupportedChainId } from 'constants/chains'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import useParsedQueryString from 'hooks/useParsedQueryString'
|
||||
import usePrevious from 'hooks/usePrevious'
|
||||
import { ParsedQs } from 'qs'
|
||||
import { useCallback, useEffect, useRef } from 'react'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { ArrowDownCircle, ChevronDown } from 'react-feather'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import { useModalOpen, useToggleModal } from 'state/application/hooks'
|
||||
import { useCloseModal, useModalIsOpen, useOpenModal, useToggleModal } from 'state/application/hooks'
|
||||
import { addPopup, ApplicationModal } from 'state/application/reducer'
|
||||
import { updateConnectionError } from 'state/connection/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 { isChainAllowed, switchChain } from 'utils/switchChain'
|
||||
import { isMobile } from 'utils/userAgent'
|
||||
|
||||
const ActiveRowLinkList = styled.div`
|
||||
display: flex;
|
||||
@@ -121,20 +121,18 @@ const SelectorLabel = styled(NetworkLabel)`
|
||||
margin-right: 8px;
|
||||
}
|
||||
`
|
||||
const SelectorControls = styled.div<{ interactive: boolean }>`
|
||||
const SelectorControls = styled.div`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.bg0};
|
||||
border: 2px solid ${({ theme }) => theme.bg0};
|
||||
border-radius: 16px;
|
||||
color: ${({ theme }) => theme.text1};
|
||||
cursor: ${({ interactive }) => (interactive ? 'pointer' : 'auto')};
|
||||
display: flex;
|
||||
font-weight: 500;
|
||||
justify-content: space-between;
|
||||
padding: 6px 8px;
|
||||
`
|
||||
const SelectorLogo = styled(Logo)<{ interactive?: boolean }>`
|
||||
margin-right: ${({ interactive }) => (interactive ? 8 : 0)}px;
|
||||
const SelectorLogo = styled(Logo)`
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
margin-right: 8px;
|
||||
}
|
||||
@@ -158,6 +156,9 @@ const BridgeLabel = ({ chainId }: { chainId: SupportedChainId }) => {
|
||||
case SupportedChainId.POLYGON:
|
||||
case SupportedChainId.POLYGON_MUMBAI:
|
||||
return <Trans>Polygon Bridge</Trans>
|
||||
case SupportedChainId.CELO:
|
||||
case SupportedChainId.CELO_ALFAJORES:
|
||||
return <Trans>Portal Bridge</Trans>
|
||||
default:
|
||||
return <Trans>Bridge</Trans>
|
||||
}
|
||||
@@ -173,6 +174,9 @@ const ExplorerLabel = ({ chainId }: { chainId: SupportedChainId }) => {
|
||||
case SupportedChainId.POLYGON:
|
||||
case SupportedChainId.POLYGON_MUMBAI:
|
||||
return <Trans>Polygonscan</Trans>
|
||||
case SupportedChainId.CELO:
|
||||
case SupportedChainId.CELO_ALFAJORES:
|
||||
return <Trans>Blockscout</Trans>
|
||||
default:
|
||||
return <Trans>Etherscan</Trans>
|
||||
}
|
||||
@@ -185,12 +189,12 @@ function Row({
|
||||
targetChain: SupportedChainId
|
||||
onSelectChain: (targetChain: number) => void
|
||||
}) {
|
||||
const { provider, chainId } = useActiveWeb3React()
|
||||
const { provider, chainId } = useWeb3React()
|
||||
if (!provider || !chainId) {
|
||||
return null
|
||||
}
|
||||
const active = chainId === targetChain
|
||||
const { helpCenterUrl, explorer, bridge, label, logoUrl } = CHAIN_INFO[targetChain]
|
||||
const { helpCenterUrl, explorer, bridge, label, logoUrl } = getChainInfo(targetChain)
|
||||
|
||||
const rowContent = (
|
||||
<FlyoutRow onClick={() => onSelectChain(targetChain)} active={active}>
|
||||
@@ -242,9 +246,9 @@ function Row({
|
||||
|
||||
const getParsedChainId = (parsedQs?: ParsedQs) => {
|
||||
const chain = parsedQs?.chain
|
||||
if (!chain || typeof chain !== 'string') return { urlChain: undefined, urlChainId: undefined }
|
||||
if (!chain || typeof chain !== 'string') return
|
||||
|
||||
return { urlChain: chain.toLowerCase(), urlChainId: getChainIdFromName(chain) }
|
||||
return getChainIdFromName(chain)
|
||||
}
|
||||
|
||||
const getChainIdFromName = (name: string) => {
|
||||
@@ -263,89 +267,110 @@ const NETWORK_SELECTOR_CHAINS = [
|
||||
SupportedChainId.POLYGON,
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
SupportedChainId.CELO,
|
||||
]
|
||||
|
||||
export default function NetworkSelector() {
|
||||
const dispatch = useAppDispatch()
|
||||
const { chainId, provider, connector } = useActiveWeb3React()
|
||||
|
||||
const { chainId, provider, connector, isActive } = useWeb3React()
|
||||
const [previousChainId, setPreviousChainId] = useState<number | undefined>(undefined)
|
||||
|
||||
// Can't use `usePrevious` because `chainId` can be undefined while activating.
|
||||
useEffect(() => {
|
||||
if (chainId && chainId !== previousChainId) {
|
||||
setPreviousChainId(chainId)
|
||||
}
|
||||
}, [chainId, previousChainId])
|
||||
|
||||
const parsedQs = useParsedQueryString()
|
||||
const { urlChain, urlChainId } = getParsedChainId(parsedQs)
|
||||
const prevChainId = usePrevious(chainId)
|
||||
const node = useRef<HTMLDivElement>()
|
||||
const open = useModalOpen(ApplicationModal.NETWORK_SELECTOR)
|
||||
const toggle = useToggleModal(ApplicationModal.NETWORK_SELECTOR)
|
||||
useOnClickOutside(node, open ? toggle : undefined)
|
||||
const urlChainId = getParsedChainId(parsedQs)
|
||||
const previousUrlChainId = usePrevious(urlChainId)
|
||||
|
||||
const history = useHistory()
|
||||
|
||||
const info = chainId ? CHAIN_INFO[chainId] : undefined
|
||||
const node = useRef<HTMLDivElement>(null)
|
||||
const isOpen = useModalIsOpen(ApplicationModal.NETWORK_SELECTOR)
|
||||
const openModal = useOpenModal(ApplicationModal.NETWORK_SELECTOR)
|
||||
const closeModal = useCloseModal(ApplicationModal.NETWORK_SELECTOR)
|
||||
const toggleModal = useToggleModal(ApplicationModal.NETWORK_SELECTOR)
|
||||
|
||||
const info = getChainInfo(chainId)
|
||||
|
||||
const replaceURLChainParam = useCallback(() => {
|
||||
if (chainId) {
|
||||
history.replace({ search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(chainId)) })
|
||||
}
|
||||
}, [chainId, history])
|
||||
|
||||
const onSelectChain = useCallback(
|
||||
async (targetChain: number, skipToggle?: boolean) => {
|
||||
async (targetChain: SupportedChainId, skipClose?: boolean) => {
|
||||
if (!connector) return
|
||||
|
||||
const wallet = getWalletForConnector(connector)
|
||||
const connectionType = getConnection(connector).type
|
||||
|
||||
try {
|
||||
dispatch(updateWalletError({ wallet, error: undefined }))
|
||||
dispatch(updateConnectionError({ connectionType, 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)
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
dispatch(updateWalletError({ wallet, error: error.message }))
|
||||
dispatch(updateConnectionError({ connectionType, error: error.message }))
|
||||
dispatch(addPopup({ content: { failedSwitchNetwork: targetChain }, key: `failed-network-switch` }))
|
||||
|
||||
// If we activate a chain and it fails, reset the query param to the current chainId
|
||||
replaceURLChainParam()
|
||||
}
|
||||
|
||||
if (!skipClose) {
|
||||
closeModal()
|
||||
}
|
||||
},
|
||||
[connector, toggle, dispatch, history, chainId]
|
||||
[connector, closeModal, dispatch, replaceURLChainParam]
|
||||
)
|
||||
|
||||
// If there is no chain query param, set it to the current chain
|
||||
useEffect(() => {
|
||||
if (!chainId || !prevChainId) return
|
||||
const chainQueryUnpopulated = !urlChainId
|
||||
if (chainQueryUnpopulated && chainId) {
|
||||
replaceURLChainParam()
|
||||
}
|
||||
}, [chainId, urlChainId, replaceURLChainParam])
|
||||
|
||||
// when network change originates from wallet or dropdown selector, just update URL
|
||||
if (chainId !== prevChainId) {
|
||||
history.replace({ search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(chainId)) })
|
||||
// otherwise assume network change originates from URL
|
||||
} else if (urlChainId && urlChainId !== chainId) {
|
||||
// If the chain changed but the query param is stale, update to the current chain
|
||||
useEffect(() => {
|
||||
const chainChanged = chainId !== previousChainId
|
||||
const chainQueryStale = urlChainId !== chainId
|
||||
if (chainChanged && chainQueryStale) {
|
||||
replaceURLChainParam()
|
||||
}
|
||||
}, [chainId, previousChainId, replaceURLChainParam, urlChainId])
|
||||
|
||||
// If the query param changed, and the chain didn't change, then activate the new chain
|
||||
useEffect(() => {
|
||||
const chainQueryManuallyUpdated = urlChainId && urlChainId !== previousUrlChainId
|
||||
if (chainQueryManuallyUpdated && isActive) {
|
||||
onSelectChain(urlChainId, true)
|
||||
}
|
||||
}, [chainId, urlChainId, prevChainId, onSelectChain, history])
|
||||
|
||||
// set chain parameter on initial load if not there
|
||||
useEffect(() => {
|
||||
if (chainId && !urlChainId) {
|
||||
history.replace({ search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(chainId)) })
|
||||
}
|
||||
}, [chainId, history, urlChainId, urlChain])
|
||||
}, [onSelectChain, urlChainId, previousUrlChainId, isActive])
|
||||
|
||||
if (!chainId || !info || !provider) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<SelectorWrapper ref={node as any} onMouseEnter={toggle} onMouseLeave={toggle}>
|
||||
<SelectorControls interactive>
|
||||
<SelectorLogo interactive src={info.logoUrl} />
|
||||
<SelectorWrapper
|
||||
ref={node}
|
||||
onMouseEnter={openModal}
|
||||
onMouseLeave={closeModal}
|
||||
onClick={isMobile ? toggleModal : undefined}
|
||||
>
|
||||
<SelectorControls>
|
||||
<SelectorLogo src={info.logoUrl} />
|
||||
<SelectorLabel>{info.label}</SelectorLabel>
|
||||
<StyledChevronDown />
|
||||
</SelectorControls>
|
||||
{open && (
|
||||
{isOpen && (
|
||||
<FlyoutMenu>
|
||||
<FlyoutMenuContents>
|
||||
<FlyoutHeader>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { RowFixed } from 'components/Row'
|
||||
import { CHAIN_INFO } from 'constants/chainInfo'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
|
||||
import useGasPrice from 'hooks/useGasPrice'
|
||||
import useMachineTimeMs from 'hooks/useMachineTime'
|
||||
@@ -100,7 +100,7 @@ const DEFAULT_MS_BEFORE_WARNING = ms`10m`
|
||||
const NETWORK_HEALTH_CHECK_MS = ms`10s`
|
||||
|
||||
export default function Polling() {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId } = useWeb3React()
|
||||
const blockNumber = useBlockNumber()
|
||||
const [isMounting, setIsMounting] = useState(false)
|
||||
const [isHover, setIsHover] = useState(false)
|
||||
@@ -112,7 +112,7 @@ export default function Polling() {
|
||||
const priceGwei = ethGasPrice ? JSBI.divide(ethGasPrice, JSBI.BigInt(1000000000)) : undefined
|
||||
|
||||
const waitMsBeforeWarning =
|
||||
(chainId ? CHAIN_INFO[chainId]?.blockWaitMsBeforeWarning : DEFAULT_MS_BEFORE_WARNING) ?? DEFAULT_MS_BEFORE_WARNING
|
||||
(chainId ? getChainInfo(chainId)?.blockWaitMsBeforeWarning : DEFAULT_MS_BEFORE_WARNING) ?? DEFAULT_MS_BEFORE_WARNING
|
||||
|
||||
const warning = Boolean(!!blockTime && machineTime - blockTime.mul(1000).toNumber() > waitMsBeforeWarning)
|
||||
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import useScrollPosition from '@react-hook/window-scroll'
|
||||
import { CHAIN_INFO } from 'constants/chainInfo'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { getChainInfoOrDefault } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import { darken } from 'polished'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { Text } from 'rebass'
|
||||
import { useShowClaimPopup, useToggleSelfClaimModal } from 'state/application/hooks'
|
||||
import { useUserHasAvailableClaim } from 'state/claim/hooks'
|
||||
import { useNativeCurrencyBalances } from 'state/connection/hooks'
|
||||
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'
|
||||
@@ -99,7 +98,7 @@ const HeaderLinks = styled(Row)`
|
||||
overflow: auto;
|
||||
align-items: center;
|
||||
${({ theme }) => theme.mediaWidth.upToLarge`
|
||||
justify-self: start;
|
||||
justify-self: start;
|
||||
`};
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
justify-self: center;
|
||||
@@ -247,9 +246,7 @@ const StyledExternalLink = styled(ExternalLink).attrs({
|
||||
`
|
||||
|
||||
export default function Header() {
|
||||
const { account, chainId, connector } = useActiveWeb3React()
|
||||
|
||||
const chainAllowed = chainId && isChainAllowed(connector, chainId)
|
||||
const { account, chainId } = useWeb3React()
|
||||
|
||||
const userEthBalance = useNativeCurrencyBalances(account ? [account] : [])?.[account ?? '']
|
||||
const [darkMode] = useDarkModeManager()
|
||||
@@ -268,7 +265,7 @@ export default function Header() {
|
||||
const {
|
||||
infoLink,
|
||||
nativeCurrency: { symbol: nativeCurrencySymbol },
|
||||
} = CHAIN_INFO[!chainId || !chainAllowed ? SupportedChainId.MAINNET : chainId]
|
||||
} = getChainInfoOrDefault(chainId)
|
||||
|
||||
return (
|
||||
<HeaderFrame showBackground={scrollY > 45}>
|
||||
@@ -284,6 +281,7 @@ export default function Header() {
|
||||
<Trans>Swap</Trans>
|
||||
</StyledNavLink>
|
||||
<StyledNavLink
|
||||
data-cy="pool-nav-link"
|
||||
id={`pool-nav-link`}
|
||||
to={'/pool'}
|
||||
isActive={(match, { pathname }) =>
|
||||
|
||||
@@ -1,22 +1,42 @@
|
||||
import { Connector } from '@web3-react/types'
|
||||
import { ConnectionType } from 'connection'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
|
||||
import FortmaticIcon from '../../assets/images/fortmaticIcon.png'
|
||||
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
|
||||
import { coinbaseWallet, fortmatic, injected, walletConnect } from '../../connectors'
|
||||
import Identicon from '../Identicon'
|
||||
|
||||
export default function StatusIcon({ connector }: { connector: Connector }) {
|
||||
switch (connector) {
|
||||
case injected:
|
||||
return <Identicon />
|
||||
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" />
|
||||
default:
|
||||
return null
|
||||
const IconWrapper = styled.div<{ size?: number }>`
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 8px;
|
||||
& > img,
|
||||
span {
|
||||
height: ${({ size }) => (size ? size + 'px' : '32px')};
|
||||
width: ${({ size }) => (size ? size + 'px' : '32px')};
|
||||
}
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
align-items: flex-end;
|
||||
`};
|
||||
`
|
||||
|
||||
export default function StatusIcon({ connectionType }: { connectionType: ConnectionType }) {
|
||||
let image
|
||||
switch (connectionType) {
|
||||
case ConnectionType.INJECTED:
|
||||
image = <Identicon />
|
||||
break
|
||||
case ConnectionType.WALLET_CONNECT:
|
||||
image = <img src={WalletConnectIcon} alt="WalletConnect" />
|
||||
break
|
||||
case ConnectionType.COINBASE_WALLET:
|
||||
image = <img src={CoinbaseWalletIcon} alt="Coinbase Wallet" />
|
||||
break
|
||||
case ConnectionType.FORTMATIC:
|
||||
image = <img src={FortmaticIcon} alt="Fortmatic" />
|
||||
break
|
||||
}
|
||||
|
||||
return <IconWrapper size={16}>{image}</IconWrapper>
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import jazzicon from '@metamask/jazzicon'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import useENSAvatar from 'hooks/useENSAvatar'
|
||||
import { useLayoutEffect, useMemo, useRef, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
@@ -19,7 +19,7 @@ const StyledAvatar = styled.img`
|
||||
`
|
||||
|
||||
export default function Identicon() {
|
||||
const { account } = useActiveWeb3React()
|
||||
const { account } = useWeb3React()
|
||||
const { avatar } = useENSAvatar(account ?? undefined)
|
||||
const [fetchable, setFetchable] = useState(true)
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ export default function LiquidityChartRangeInput({
|
||||
})
|
||||
|
||||
const onBrushDomainChangeEnded = useCallback(
|
||||
(domain, mode) => {
|
||||
(domain: [number, number], mode: string | undefined) => {
|
||||
let leftRangeValue = Number(domain[0])
|
||||
const rightRangeValue = Number(domain[1])
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { PrivacyPolicyModal } from 'components/PrivacyPolicy'
|
||||
import { L2_CHAIN_IDS } from 'constants/chains'
|
||||
import { LOCALE_LABEL, SUPPORTED_LOCALES, SupportedLocale } from 'constants/locales'
|
||||
import { useActiveLocale } from 'hooks/useActiveLocale'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useLocationLinkProps } from 'hooks/useLocationLinkProps'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { FunctionComponent, PropsWithChildren, useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
BookOpen,
|
||||
Check,
|
||||
@@ -26,7 +26,7 @@ import styled, { css } from 'styled-components/macro'
|
||||
|
||||
import { ReactComponent as MenuIcon } from '../../assets/images/menu.svg'
|
||||
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
|
||||
import { useModalOpen, useToggleModal } from '../../state/application/hooks'
|
||||
import { useModalIsOpen, useToggleModal } from '../../state/application/hooks'
|
||||
import { ApplicationModal } from '../../state/application/reducer'
|
||||
import { ExternalLink } from '../../theme'
|
||||
import { ButtonPrimary } from '../Button'
|
||||
@@ -207,10 +207,10 @@ function LanguageMenu({ close }: { close: () => void }) {
|
||||
}
|
||||
|
||||
export default function Menu() {
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
const { account, chainId } = useWeb3React()
|
||||
|
||||
const node = useRef<HTMLDivElement>()
|
||||
const open = useModalOpen(ApplicationModal.MENU)
|
||||
const open = useModalIsOpen(ApplicationModal.MENU)
|
||||
const toggleMenu = useToggleModal(ApplicationModal.MENU)
|
||||
useOnClickOutside(node, open ? toggleMenu : undefined)
|
||||
const togglePrivacyPolicy = useToggleModal(ApplicationModal.PRIVACY_POLICY)
|
||||
@@ -311,7 +311,7 @@ export default function Menu() {
|
||||
|
||||
interface NewMenuProps {
|
||||
flyoutAlignment?: FlyoutAlignment
|
||||
ToggleUI?: React.FunctionComponent
|
||||
ToggleUI?: FunctionComponent<PropsWithChildren<unknown>>
|
||||
menuItems: {
|
||||
content: any
|
||||
link: string
|
||||
@@ -334,7 +334,7 @@ const ExternalMenuItem = styled(MenuItem)`
|
||||
|
||||
export const NewMenu = ({ flyoutAlignment = FlyoutAlignment.RIGHT, ToggleUI, menuItems, ...rest }: NewMenuProps) => {
|
||||
const node = useRef<HTMLDivElement>()
|
||||
const open = useModalOpen(ApplicationModal.POOL_OVERVIEW_OPTIONS)
|
||||
const open = useModalIsOpen(ApplicationModal.POOL_OVERVIEW_OPTIONS)
|
||||
const toggle = useToggleModal(ApplicationModal.POOL_OVERVIEW_OPTIONS)
|
||||
useOnClickOutside(node, open ? toggle : undefined)
|
||||
const ToggleElement = ToggleUI || StyledMenuIcon
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useContext } from 'react'
|
||||
import { ArrowUpCircle } from 'react-feather'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
@@ -50,7 +50,7 @@ export function SubmittedView({
|
||||
hash: string | undefined
|
||||
}) {
|
||||
const theme = useContext(ThemeContext)
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId } = useWeb3React()
|
||||
|
||||
return (
|
||||
<ConfirmOrLoadingWrapper>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { CHAIN_INFO } from 'constants/chainInfo'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { ArrowUpRight } from 'react-feather'
|
||||
import { useDarkModeManager } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
@@ -42,6 +42,8 @@ const SHOULD_SHOW_ALERT = {
|
||||
[SupportedChainId.ARBITRUM_RINKEBY]: true,
|
||||
[SupportedChainId.POLYGON]: true,
|
||||
[SupportedChainId.POLYGON_MUMBAI]: true,
|
||||
[SupportedChainId.CELO]: true,
|
||||
[SupportedChainId.CELO_ALFAJORES]: true,
|
||||
}
|
||||
|
||||
type NetworkAlertChains = keyof typeof SHOULD_SHOW_ALERT
|
||||
@@ -54,6 +56,10 @@ const BG_COLORS_BY_DARK_MODE_AND_CHAIN_ID: {
|
||||
'radial-gradient(100% 93.36% at 0% 6.64%, rgba(160, 108, 247, 0.1) 0%, rgba(82, 32, 166, 0.1) 100%)',
|
||||
[SupportedChainId.POLYGON_MUMBAI]:
|
||||
'radial-gradient(100% 93.36% at 0% 6.64%, rgba(160, 108, 247, 0.1) 0%, rgba(82, 32, 166, 0.1) 100%)',
|
||||
[SupportedChainId.CELO]:
|
||||
'radial-gradient(182.71% 150.59% at 2.81% 7.69%, rgba(90, 190, 170, 0.15) 0%, rgba(80, 160, 40, 0.15) 100%)',
|
||||
[SupportedChainId.CELO_ALFAJORES]:
|
||||
'radial-gradient(182.71% 150.59% at 2.81% 7.69%, rgba(90, 190, 170, 0.15) 0%, rgba(80, 160, 40, 0.15) 100%)',
|
||||
[SupportedChainId.OPTIMISM]:
|
||||
'radial-gradient(948% 292% at 42% 0%, rgba(255, 58, 212, 0.01) 0%, rgba(255, 255, 255, 0.04) 100%),radial-gradient(98% 96% at 2% 0%, rgba(255, 39, 39, 0.01) 0%, rgba(235, 0, 255, 0.01) 96%)',
|
||||
[SupportedChainId.OPTIMISTIC_KOVAN]:
|
||||
@@ -68,6 +74,10 @@ const BG_COLORS_BY_DARK_MODE_AND_CHAIN_ID: {
|
||||
'radial-gradient(182.71% 205.59% at 2.81% 7.69%, rgba(130, 71, 229, 0.2) 0%, rgba(167, 202, 255, 0.2) 100%)',
|
||||
[SupportedChainId.POLYGON_MUMBAI]:
|
||||
'radial-gradient(182.71% 205.59% at 2.81% 7.69%, rgba(130, 71, 229, 0.2) 0%, rgba(167, 202, 255, 0.2) 100%)',
|
||||
[SupportedChainId.CELO]:
|
||||
'radial-gradient(182.71% 150.59% at 2.81% 7.69%, rgba(63, 208, 137, 0.15) 0%, rgba(49, 205, 50, 0.15) 100%)',
|
||||
[SupportedChainId.CELO_ALFAJORES]:
|
||||
'radial-gradient(182.71% 150.59% at 2.81% 7.69%, rgba(63, 208, 137, 0.15) 0%, rgba(49, 205, 50, 0.15) 100%)',
|
||||
[SupportedChainId.OPTIMISM]:
|
||||
'radial-gradient(92% 105% at 50% 7%, rgba(255, 58, 212, 0.04) 0%, rgba(255, 255, 255, 0.03) 100%),radial-gradient(100% 97% at 0% 12%, rgba(235, 0, 255, 0.1) 0%, rgba(243, 19, 19, 0.1) 100%), hsla(0, 0%, 100%, 0.1)',
|
||||
[SupportedChainId.OPTIMISTIC_KOVAN]:
|
||||
@@ -129,6 +139,8 @@ const StyledArrowUpRight = styled(ArrowUpRight)`
|
||||
const TEXT_COLORS: { [chainId in NetworkAlertChains]: string } = {
|
||||
[SupportedChainId.POLYGON]: 'rgba(130, 71, 229)',
|
||||
[SupportedChainId.POLYGON_MUMBAI]: 'rgba(130, 71, 229)',
|
||||
[SupportedChainId.CELO]: 'rgba(53, 178, 97)',
|
||||
[SupportedChainId.CELO_ALFAJORES]: 'rgba(53, 178, 97)',
|
||||
[SupportedChainId.OPTIMISM]: '#ff3856',
|
||||
[SupportedChainId.OPTIMISTIC_KOVAN]: '#ff3856',
|
||||
[SupportedChainId.ARBITRUM_ONE]: '#0490ed',
|
||||
@@ -140,14 +152,14 @@ function shouldShowAlert(chainId: number | undefined): chainId is NetworkAlertCh
|
||||
}
|
||||
|
||||
export function NetworkAlert() {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId } = useWeb3React()
|
||||
const [darkMode] = useDarkModeManager()
|
||||
|
||||
if (!shouldShowAlert(chainId)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const { label, logoUrl, bridge } = CHAIN_INFO[chainId]
|
||||
const { label, logoUrl, bridge } = getChainInfo(chainId)
|
||||
const textColor = TEXT_COLORS[chainId]
|
||||
|
||||
return bridge ? (
|
||||
|
||||
@@ -26,6 +26,7 @@ const Arrow = styled.div`
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
box-sizing: border-box;
|
||||
z-index: 9998;
|
||||
|
||||
content: '';
|
||||
@@ -35,7 +36,7 @@ const Arrow = styled.div`
|
||||
}
|
||||
|
||||
&.arrow-top {
|
||||
bottom: -5px;
|
||||
bottom: -4px;
|
||||
::before {
|
||||
border-top: none;
|
||||
border-left: none;
|
||||
@@ -43,7 +44,7 @@ const Arrow = styled.div`
|
||||
}
|
||||
|
||||
&.arrow-bottom {
|
||||
top: -5px;
|
||||
top: -4px;
|
||||
::before {
|
||||
border-bottom: none;
|
||||
border-right: none;
|
||||
@@ -51,7 +52,7 @@ const Arrow = styled.div`
|
||||
}
|
||||
|
||||
&.arrow-left {
|
||||
right: -5px;
|
||||
right: -4px;
|
||||
|
||||
::before {
|
||||
border-bottom: none;
|
||||
@@ -60,7 +61,7 @@ const Arrow = styled.div`
|
||||
}
|
||||
|
||||
&.arrow-right {
|
||||
left: -5px;
|
||||
left: -4px;
|
||||
::before {
|
||||
border-right: none;
|
||||
border-top: none;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { Heart, X } from 'react-feather'
|
||||
import styled, { keyframes } from 'styled-components/macro'
|
||||
|
||||
import tokenLogo from '../../assets/images/token-logo.png'
|
||||
import {
|
||||
useModalOpen,
|
||||
useModalIsOpen,
|
||||
useShowClaimPopup,
|
||||
useToggleSelfClaimModal,
|
||||
useToggleShowClaimPopup,
|
||||
@@ -55,14 +55,14 @@ const UniToken = styled.img`
|
||||
`
|
||||
|
||||
export default function ClaimPopup() {
|
||||
const { account } = useActiveWeb3React()
|
||||
const { account } = useWeb3React()
|
||||
|
||||
// dont store these in persisted state yet
|
||||
const showClaimPopup: boolean = useShowClaimPopup()
|
||||
const toggleShowClaimPopup = useToggleShowClaimPopup()
|
||||
|
||||
// toggle for showing this modal
|
||||
const showClaimModal = useModalOpen(ApplicationModal.SELF_CLAIM)
|
||||
const showClaimModal = useModalIsOpen(ApplicationModal.SELF_CLAIM)
|
||||
const toggleSelfClaimModal = useToggleSelfClaimModal()
|
||||
const handleToggleSelfClaimModal = useCallback(() => {
|
||||
sendEvent({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { CHAIN_INFO } from 'constants/chainInfo'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { useContext } from 'react'
|
||||
import { AlertCircle } from 'react-feather'
|
||||
@@ -14,7 +14,7 @@ const RowNoFlex = styled(AutoRow)`
|
||||
`
|
||||
|
||||
export default function FailedNetworkSwitchPopup({ chainId }: { chainId: SupportedChainId }) {
|
||||
const chainInfo = CHAIN_INFO[chainId]
|
||||
const chainInfo = getChainInfo(chainId)
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useContext } from 'react'
|
||||
import { AlertCircle, CheckCircle } from 'react-feather'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
@@ -16,7 +16,7 @@ const RowNoFlex = styled(AutoRow)`
|
||||
`
|
||||
|
||||
export default function TransactionPopup({ hash }: { hash: string }) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId } = useWeb3React()
|
||||
|
||||
const tx = useTransaction(hash)
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import styled from 'styled-components/macro'
|
||||
import { MEDIA_WIDTHS } from 'theme'
|
||||
|
||||
@@ -63,7 +63,7 @@ export default function Popups() {
|
||||
const urlWarningActive = useURLWarningVisible()
|
||||
|
||||
// need extra padding if network is not L1 Ethereum
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId } = useWeb3React()
|
||||
const isNotOnMainnet = Boolean(chainId && chainId !== SupportedChainId.MAINNET)
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import JSBI from 'jsbi'
|
||||
import { transparentize } from 'polished'
|
||||
import { useState } from 'react'
|
||||
@@ -13,7 +13,7 @@ import styled from 'styled-components/macro'
|
||||
import { BIG_INT_ZERO } from '../../constants/misc'
|
||||
import { useColor } from '../../hooks/useColor'
|
||||
import { useTotalSupply } from '../../hooks/useTotalSupply'
|
||||
import { useTokenBalance } from '../../state/wallet/hooks'
|
||||
import { useTokenBalance } from '../../state/connection/hooks'
|
||||
import { currencyId } from '../../utils/currencyId'
|
||||
import { unwrappedToken } from '../../utils/unwrappedToken'
|
||||
import { ButtonEmpty, ButtonPrimary, ButtonSecondary } from '../Button'
|
||||
@@ -42,7 +42,7 @@ interface PositionCardProps {
|
||||
}
|
||||
|
||||
export default function V2PositionCard({ pair, border, stakedBalance }: PositionCardProps) {
|
||||
const { account } = useActiveWeb3React()
|
||||
const { account } = useWeb3React()
|
||||
|
||||
const currency0 = unwrappedToken(pair.token0)
|
||||
const currency1 = unwrappedToken(pair.token1)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import JSBI from 'jsbi'
|
||||
import { transparentize } from 'polished'
|
||||
import { useState } from 'react'
|
||||
@@ -13,7 +13,7 @@ import styled from 'styled-components/macro'
|
||||
import { BIG_INT_ZERO } from '../../constants/misc'
|
||||
import { useColor } from '../../hooks/useColor'
|
||||
import { useTotalSupply } from '../../hooks/useTotalSupply'
|
||||
import { useTokenBalance } from '../../state/wallet/hooks'
|
||||
import { useTokenBalance } from '../../state/connection/hooks'
|
||||
import { ExternalLink, ThemedText } from '../../theme'
|
||||
import { currencyId } from '../../utils/currencyId'
|
||||
import { unwrappedToken } from '../../utils/unwrappedToken'
|
||||
@@ -46,7 +46,7 @@ interface PositionCardProps {
|
||||
}
|
||||
|
||||
export function MinimalPositionCard({ pair, showUnwrapped = false, border }: PositionCardProps) {
|
||||
const { account } = useActiveWeb3React()
|
||||
const { account } = useWeb3React()
|
||||
|
||||
const currency0 = showUnwrapped ? pair.token0 : unwrappedToken(pair.token0)
|
||||
const currency1 = showUnwrapped ? pair.token1 : unwrappedToken(pair.token1)
|
||||
@@ -158,7 +158,7 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
|
||||
}
|
||||
|
||||
export default function FullPositionCard({ pair, border, stakedBalance }: PositionCardProps) {
|
||||
const { account } = useActiveWeb3React()
|
||||
const { account } = useWeb3React()
|
||||
|
||||
const currency0 = unwrappedToken(pair.token0)
|
||||
const currency1 = unwrappedToken(pair.token1)
|
||||
|
||||
@@ -8,7 +8,7 @@ import styled from 'styled-components/macro'
|
||||
import { ExternalLink, ThemedText } from 'theme'
|
||||
import { isMobile } from 'utils/userAgent'
|
||||
|
||||
import { useModalOpen, useTogglePrivacyPolicy } from '../../state/application/hooks'
|
||||
import { useModalIsOpen, useTogglePrivacyPolicy } from '../../state/application/hooks'
|
||||
import { ApplicationModal } from '../../state/application/reducer'
|
||||
import { AutoColumn } from '../Column'
|
||||
import Modal from '../Modal'
|
||||
@@ -70,7 +70,7 @@ const EXTERNAL_APIS = [
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Google Analytics',
|
||||
name: 'Google Analytics & Amplitude',
|
||||
description: <Trans>The app logs anonymized usage statistics in order to improve over time.</Trans>,
|
||||
},
|
||||
{
|
||||
@@ -81,7 +81,7 @@ const EXTERNAL_APIS = [
|
||||
|
||||
export function PrivacyPolicyModal() {
|
||||
const node = useRef<HTMLDivElement>()
|
||||
const open = useModalOpen(ApplicationModal.PRIVACY_POLICY)
|
||||
const open = useModalIsOpen(ApplicationModal.PRIVACY_POLICY)
|
||||
const toggle = useTogglePrivacyPolicy()
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Protocol } from '@uniswap/router-sdk'
|
||||
import { Currency, Percent } from '@uniswap/sdk-core'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { RoutingDiagramEntry } from 'components/swap/SwapRoute'
|
||||
import { DAI, USDC_MAINNET, WBTC } from 'constants/tokens'
|
||||
import { render } from 'test-utils'
|
||||
|
||||
import RoutingDiagram, { RoutingDiagramEntry } from './RoutingDiagram'
|
||||
import RoutingDiagram from './RoutingDiagram'
|
||||
|
||||
const percent = (strings: TemplateStringsArray) => new Percent(parseInt(strings[0]), 100)
|
||||
|
||||
|
||||
@@ -3,45 +3,45 @@
|
||||
exports[`renders multi route 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="RoutingDiagram__Wrapper-sc-i2tbb-0 ivndgC css-vurnku"
|
||||
class="RoutingDiagram__Wrapper-sc-o1ook0-0 kfWRgd css-vurnku"
|
||||
>
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 RoutingDiagram__RouteContainerRow-sc-i2tbb-1 lmTMKd hLLNig hDkZVB"
|
||||
class="sc-bczRLJ Row-sc-nrd8cx-0 RoutingDiagram__RouteContainerRow-sc-o1ook0-1 jITePI cSETNY dmzxCy"
|
||||
>
|
||||
CurrencyLogo currency=USDC
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 RoutingDiagram__RouteRow-sc-i2tbb-2 lmTMKd hLLNig hUDqOH"
|
||||
class="sc-bczRLJ Row-sc-nrd8cx-0 RoutingDiagram__RouteRow-sc-o1ook0-2 jITePI cSETNY fclIfk"
|
||||
>
|
||||
<div
|
||||
class="RoutingDiagram__DottedLine-sc-i2tbb-4 cKqYfU"
|
||||
class="RoutingDiagram__DottedLine-sc-o1ook0-4 iRYKBb"
|
||||
>
|
||||
<svg
|
||||
class="RoutingDiagram__DotColor-sc-i2tbb-5 fhSaBA"
|
||||
class="RoutingDiagram__DotColor-sc-o1ook0-5 fEbpBT"
|
||||
>
|
||||
dot_line.svg
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="Badge-sc-3epor3-0 RoutingDiagram__OpaqueBadge-sc-i2tbb-6 knpfHF gGARxH"
|
||||
class="Badge-sc-1mhw5si-0 RoutingDiagram__OpaqueBadge-sc-o1ook0-6 gbzyaI iSegVg"
|
||||
>
|
||||
<div
|
||||
class="Badge-sc-3epor3-0 RoutingDiagram__ProtocolBadge-sc-i2tbb-7 knpfHF lbdUti"
|
||||
class="Badge-sc-1mhw5si-0 RoutingDiagram__ProtocolBadge-sc-o1ook0-7 gbzyaI lidWLN"
|
||||
>
|
||||
<div
|
||||
class="theme__TextWrapper-sc-5lu8um-0 chxxqs RoutingDiagram__BadgeText-sc-i2tbb-8 ijjHig css-15li2d9"
|
||||
class="theme__TextWrapper-sc-18nh1jk-0 chnOFO RoutingDiagram__BadgeText-sc-o1ook0-8 iwzpuz css-15li2d9"
|
||||
>
|
||||
V2
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="theme__TextWrapper-sc-5lu8um-0 chxxqs RoutingDiagram__BadgeText-sc-i2tbb-8 ijjHig css-1aekuku"
|
||||
class="theme__TextWrapper-sc-18nh1jk-0 chnOFO RoutingDiagram__BadgeText-sc-o1ook0-8 iwzpuz css-1aekuku"
|
||||
style="min-width: auto;"
|
||||
>
|
||||
75%
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 Row__AutoRow-sc-u7azg8-3 iqvZFe hLLNig cUhARX"
|
||||
class="sc-bczRLJ Row-sc-nrd8cx-0 Row__AutoRow-sc-nrd8cx-3 cYXLjH cSETNY dllMrH"
|
||||
style="justify-content: space-evenly; z-index: 2;"
|
||||
width="100%"
|
||||
>
|
||||
@@ -51,42 +51,42 @@ exports[`renders multi route 1`] = `
|
||||
CurrencyLogo currency=DAI
|
||||
</div>
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 RoutingDiagram__RouteContainerRow-sc-i2tbb-1 lmTMKd hLLNig hDkZVB"
|
||||
class="sc-bczRLJ Row-sc-nrd8cx-0 RoutingDiagram__RouteContainerRow-sc-o1ook0-1 jITePI cSETNY dmzxCy"
|
||||
>
|
||||
CurrencyLogo currency=USDC
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 RoutingDiagram__RouteRow-sc-i2tbb-2 lmTMKd hLLNig hUDqOH"
|
||||
class="sc-bczRLJ Row-sc-nrd8cx-0 RoutingDiagram__RouteRow-sc-o1ook0-2 jITePI cSETNY fclIfk"
|
||||
>
|
||||
<div
|
||||
class="RoutingDiagram__DottedLine-sc-i2tbb-4 cKqYfU"
|
||||
class="RoutingDiagram__DottedLine-sc-o1ook0-4 iRYKBb"
|
||||
>
|
||||
<svg
|
||||
class="RoutingDiagram__DotColor-sc-i2tbb-5 fhSaBA"
|
||||
class="RoutingDiagram__DotColor-sc-o1ook0-5 fEbpBT"
|
||||
>
|
||||
dot_line.svg
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="Badge-sc-3epor3-0 RoutingDiagram__OpaqueBadge-sc-i2tbb-6 knpfHF gGARxH"
|
||||
class="Badge-sc-1mhw5si-0 RoutingDiagram__OpaqueBadge-sc-o1ook0-6 gbzyaI iSegVg"
|
||||
>
|
||||
<div
|
||||
class="Badge-sc-3epor3-0 RoutingDiagram__ProtocolBadge-sc-i2tbb-7 knpfHF lbdUti"
|
||||
class="Badge-sc-1mhw5si-0 RoutingDiagram__ProtocolBadge-sc-o1ook0-7 gbzyaI lidWLN"
|
||||
>
|
||||
<div
|
||||
class="theme__TextWrapper-sc-5lu8um-0 chxxqs RoutingDiagram__BadgeText-sc-i2tbb-8 ijjHig css-15li2d9"
|
||||
class="theme__TextWrapper-sc-18nh1jk-0 chnOFO RoutingDiagram__BadgeText-sc-o1ook0-8 iwzpuz css-15li2d9"
|
||||
>
|
||||
V3
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="theme__TextWrapper-sc-5lu8um-0 chxxqs RoutingDiagram__BadgeText-sc-i2tbb-8 ijjHig css-1aekuku"
|
||||
class="theme__TextWrapper-sc-18nh1jk-0 chnOFO RoutingDiagram__BadgeText-sc-o1ook0-8 iwzpuz css-1aekuku"
|
||||
style="min-width: auto;"
|
||||
>
|
||||
25%
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 Row__AutoRow-sc-u7azg8-3 iqvZFe hLLNig cUhARX"
|
||||
class="sc-bczRLJ Row-sc-nrd8cx-0 Row__AutoRow-sc-nrd8cx-3 cYXLjH cSETNY dllMrH"
|
||||
style="justify-content: space-evenly; z-index: 2;"
|
||||
width="100%"
|
||||
>
|
||||
@@ -102,45 +102,45 @@ exports[`renders multi route 1`] = `
|
||||
exports[`renders single route 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="RoutingDiagram__Wrapper-sc-i2tbb-0 ivndgC css-vurnku"
|
||||
class="RoutingDiagram__Wrapper-sc-o1ook0-0 kfWRgd css-vurnku"
|
||||
>
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 RoutingDiagram__RouteContainerRow-sc-i2tbb-1 lmTMKd hLLNig hDkZVB"
|
||||
class="sc-bczRLJ Row-sc-nrd8cx-0 RoutingDiagram__RouteContainerRow-sc-o1ook0-1 jITePI cSETNY dmzxCy"
|
||||
>
|
||||
CurrencyLogo currency=USDC
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 RoutingDiagram__RouteRow-sc-i2tbb-2 lmTMKd hLLNig hUDqOH"
|
||||
class="sc-bczRLJ Row-sc-nrd8cx-0 RoutingDiagram__RouteRow-sc-o1ook0-2 jITePI cSETNY fclIfk"
|
||||
>
|
||||
<div
|
||||
class="RoutingDiagram__DottedLine-sc-i2tbb-4 cKqYfU"
|
||||
class="RoutingDiagram__DottedLine-sc-o1ook0-4 iRYKBb"
|
||||
>
|
||||
<svg
|
||||
class="RoutingDiagram__DotColor-sc-i2tbb-5 fhSaBA"
|
||||
class="RoutingDiagram__DotColor-sc-o1ook0-5 fEbpBT"
|
||||
>
|
||||
dot_line.svg
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="Badge-sc-3epor3-0 RoutingDiagram__OpaqueBadge-sc-i2tbb-6 knpfHF gGARxH"
|
||||
class="Badge-sc-1mhw5si-0 RoutingDiagram__OpaqueBadge-sc-o1ook0-6 gbzyaI iSegVg"
|
||||
>
|
||||
<div
|
||||
class="Badge-sc-3epor3-0 RoutingDiagram__ProtocolBadge-sc-i2tbb-7 knpfHF lbdUti"
|
||||
class="Badge-sc-1mhw5si-0 RoutingDiagram__ProtocolBadge-sc-o1ook0-7 gbzyaI lidWLN"
|
||||
>
|
||||
<div
|
||||
class="theme__TextWrapper-sc-5lu8um-0 chxxqs RoutingDiagram__BadgeText-sc-i2tbb-8 ijjHig css-15li2d9"
|
||||
class="theme__TextWrapper-sc-18nh1jk-0 chnOFO RoutingDiagram__BadgeText-sc-o1ook0-8 iwzpuz css-15li2d9"
|
||||
>
|
||||
V3
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="theme__TextWrapper-sc-5lu8um-0 chxxqs RoutingDiagram__BadgeText-sc-i2tbb-8 ijjHig css-1aekuku"
|
||||
class="theme__TextWrapper-sc-18nh1jk-0 chnOFO RoutingDiagram__BadgeText-sc-o1ook0-8 iwzpuz css-1aekuku"
|
||||
style="min-width: auto;"
|
||||
>
|
||||
100%
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 Row__AutoRow-sc-u7azg8-3 iqvZFe hLLNig cUhARX"
|
||||
class="sc-bczRLJ Row-sc-nrd8cx-0 Row__AutoRow-sc-nrd8cx-3 cYXLjH cSETNY dllMrH"
|
||||
style="justify-content: space-evenly; z-index: 2;"
|
||||
width="100%"
|
||||
>
|
||||
@@ -156,7 +156,7 @@ exports[`renders single route 1`] = `
|
||||
exports[`renders when no routes are provided 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="RoutingDiagram__Wrapper-sc-i2tbb-0 ivndgC css-vurnku"
|
||||
class="RoutingDiagram__Wrapper-sc-o1ook0-0 kfWRgd css-vurnku"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { Currency, Token } from '@uniswap/sdk-core'
|
||||
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
|
||||
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import { AutoRow } from 'components/Row'
|
||||
@@ -31,14 +33,35 @@ const BaseWrapper = styled.div<{ disable?: boolean }>`
|
||||
filter: ${({ disable }) => disable && 'grayscale(1)'};
|
||||
`
|
||||
|
||||
const formatAnalyticsEventProperties = (
|
||||
currency: Currency,
|
||||
tokenAddress: string | undefined,
|
||||
searchQuery: string,
|
||||
isAddressSearch: string | false
|
||||
) => ({
|
||||
token_symbol: currency?.symbol,
|
||||
token_chain_id: currency?.chainId,
|
||||
...(tokenAddress ? { token_address: tokenAddress } : {}),
|
||||
is_suggested_token: true,
|
||||
is_selected_from_list: false,
|
||||
is_imported_by_user: false,
|
||||
...(isAddressSearch === false
|
||||
? { search_token_symbol_input: searchQuery }
|
||||
: { search_token_address_input: isAddressSearch }),
|
||||
})
|
||||
|
||||
export default function CommonBases({
|
||||
chainId,
|
||||
onSelect,
|
||||
selectedCurrency,
|
||||
searchQuery,
|
||||
isAddressSearch,
|
||||
}: {
|
||||
chainId?: number
|
||||
selectedCurrency?: Currency | null
|
||||
onSelect: (currency: Currency) => void
|
||||
searchQuery: string
|
||||
isAddressSearch: string | false
|
||||
}) {
|
||||
const bases = typeof chainId !== 'undefined' ? COMMON_BASES[chainId] ?? [] : []
|
||||
|
||||
@@ -47,19 +70,29 @@ export default function CommonBases({
|
||||
<AutoRow gap="4px">
|
||||
{bases.map((currency: Currency) => {
|
||||
const isSelected = selectedCurrency?.equals(currency)
|
||||
const tokenAddress = currency instanceof Token ? currency?.address : undefined
|
||||
|
||||
return (
|
||||
<BaseWrapper
|
||||
tabIndex={0}
|
||||
onKeyPress={(e) => !isSelected && e.key === 'Enter' && onSelect(currency)}
|
||||
onClick={() => !isSelected && onSelect(currency)}
|
||||
disable={isSelected}
|
||||
<TraceEvent
|
||||
events={[Event.onClick, Event.onKeyPress]}
|
||||
name={EventName.TOKEN_SELECTED}
|
||||
properties={formatAnalyticsEventProperties(currency, tokenAddress, searchQuery, isAddressSearch)}
|
||||
element={ElementName.COMMON_BASES_CURRENCY_BUTTON}
|
||||
key={currencyId(currency)}
|
||||
>
|
||||
<CurrencyLogoFromList currency={currency} />
|
||||
<Text fontWeight={500} fontSize={16}>
|
||||
{currency.symbol}
|
||||
</Text>
|
||||
</BaseWrapper>
|
||||
<BaseWrapper
|
||||
tabIndex={0}
|
||||
onKeyPress={(e) => !isSelected && e.key === 'Enter' && onSelect(currency)}
|
||||
onClick={() => !isSelected && onSelect(currency)}
|
||||
disable={isSelected}
|
||||
key={currencyId(currency)}
|
||||
>
|
||||
<CurrencyLogoFromList currency={currency} />
|
||||
<Text fontWeight={500} fontSize={16}>
|
||||
{currency.symbol}
|
||||
</Text>
|
||||
</BaseWrapper>
|
||||
</TraceEvent>
|
||||
)
|
||||
})}
|
||||
</AutoRow>
|
||||
|
||||
@@ -9,13 +9,13 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
style="height: 168px; width: 100%;"
|
||||
>
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 Row__RowBetween-sc-u7azg8-1 styleds__MenuItem-sc-muzgnq-3 lmTMKd hLLNig hzJkYd firMKT token-item-0x6B175474E89094C44Da98b954EedeAC495271d0F"
|
||||
class="sc-bczRLJ Row-sc-nrd8cx-0 Row__RowBetween-sc-nrd8cx-1 styleds__MenuItem-sc-1xp9ndq-3 jITePI cSETNY ekbhWd dxhejL token-item-0x6B175474E89094C44Da98b954EedeAC495271d0F"
|
||||
style="position: absolute; left: 0px; top: 0px; height: 56px; width: 100%;"
|
||||
tabindex="0"
|
||||
>
|
||||
CurrencyLogo currency=DAI
|
||||
<div
|
||||
class="Column-sc-1r2yyln-0 cYEAJI"
|
||||
class="Column-sc-1kykgp9-0 gdySCE"
|
||||
>
|
||||
<div
|
||||
class="css-8mokm4"
|
||||
@@ -24,7 +24,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
DAI
|
||||
</div>
|
||||
<div
|
||||
class="theme__TextWrapper-sc-5lu8um-0 gVIOIC css-165qfk5"
|
||||
class="theme__TextWrapper-sc-18nh1jk-0 gykQyY css-165qfk5"
|
||||
>
|
||||
Dai Stablecoin
|
||||
</div>
|
||||
@@ -32,13 +32,13 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
<span />
|
||||
</div>
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 Row__RowBetween-sc-u7azg8-1 styleds__MenuItem-sc-muzgnq-3 lmTMKd hLLNig hzJkYd firMKT token-item-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
class="sc-bczRLJ Row-sc-nrd8cx-0 Row__RowBetween-sc-nrd8cx-1 styleds__MenuItem-sc-1xp9ndq-3 jITePI cSETNY ekbhWd dxhejL token-item-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
style="position: absolute; left: 0px; top: 56px; height: 56px; width: 100%;"
|
||||
tabindex="0"
|
||||
>
|
||||
CurrencyLogo currency=USDC
|
||||
<div
|
||||
class="Column-sc-1r2yyln-0 cYEAJI"
|
||||
class="Column-sc-1kykgp9-0 gdySCE"
|
||||
>
|
||||
<div
|
||||
class="css-8mokm4"
|
||||
@@ -47,7 +47,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
USDC
|
||||
</div>
|
||||
<div
|
||||
class="theme__TextWrapper-sc-5lu8um-0 gVIOIC css-165qfk5"
|
||||
class="theme__TextWrapper-sc-18nh1jk-0 gykQyY css-165qfk5"
|
||||
>
|
||||
USD//C
|
||||
</div>
|
||||
@@ -55,13 +55,13 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
<span />
|
||||
</div>
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 Row__RowBetween-sc-u7azg8-1 styleds__MenuItem-sc-muzgnq-3 lmTMKd hLLNig hzJkYd firMKT token-item-0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
|
||||
class="sc-bczRLJ Row-sc-nrd8cx-0 Row__RowBetween-sc-nrd8cx-1 styleds__MenuItem-sc-1xp9ndq-3 jITePI cSETNY ekbhWd dxhejL token-item-0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
|
||||
style="position: absolute; left: 0px; top: 112px; height: 56px; width: 100%;"
|
||||
tabindex="0"
|
||||
>
|
||||
CurrencyLogo currency=WBTC
|
||||
<div
|
||||
class="Column-sc-1r2yyln-0 cYEAJI"
|
||||
class="Column-sc-1kykgp9-0 gdySCE"
|
||||
>
|
||||
<div
|
||||
class="css-8mokm4"
|
||||
@@ -70,7 +70,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
WBTC
|
||||
</div>
|
||||
<div
|
||||
class="theme__TextWrapper-sc-5lu8um-0 gVIOIC css-165qfk5"
|
||||
class="theme__TextWrapper-sc-18nh1jk-0 gykQyY css-165qfk5"
|
||||
>
|
||||
Wrapped BTC
|
||||
</div>
|
||||
|
||||
@@ -22,20 +22,21 @@ jest.mock(
|
||||
`CurrencyLogo currency=${currency.symbol}`
|
||||
)
|
||||
|
||||
jest.mock('hooks/useActiveWeb3React', () => {
|
||||
jest.mock('@web3-react/core', () => {
|
||||
const web3React = jest.requireActual('@web3-react/core')
|
||||
return {
|
||||
__esModule: true,
|
||||
default: () => ({
|
||||
useWeb3React: () => ({
|
||||
account: '123',
|
||||
active: true,
|
||||
isActive: true,
|
||||
}),
|
||||
...web3React,
|
||||
}
|
||||
})
|
||||
|
||||
jest.mock('../../../state/wallet/hooks', () => {
|
||||
jest.mock('../../../state/connection/hooks', () => {
|
||||
return {
|
||||
useCurrencyBalance: (currency: Currency) => {
|
||||
return mockCurrencyAmt[(currency as mockToken).address]
|
||||
return mockCurrencyAmt[(currency as mockToken)?.address]
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
|
||||
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
|
||||
import { LightGreyCard } from 'components/Card'
|
||||
import QuestionHelper from 'components/QuestionHelper'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import { CSSProperties, MutableRefObject, useCallback, useMemo } from 'react'
|
||||
import { FixedSizeList } from 'react-window'
|
||||
@@ -11,9 +13,9 @@ import styled from 'styled-components/macro'
|
||||
|
||||
import TokenListLogo from '../../../assets/svg/tokenlist.svg'
|
||||
import { useIsUserAddedToken } from '../../../hooks/Tokens'
|
||||
import { useCurrencyBalance } from '../../../state/connection/hooks'
|
||||
import { useCombinedActiveList } from '../../../state/lists/hooks'
|
||||
import { WrappedTokenInfo } from '../../../state/lists/wrappedTokenInfo'
|
||||
import { useCurrencyBalance } from '../../../state/wallet/hooks'
|
||||
import { ThemedText } from '../../../theme'
|
||||
import { isTokenOnList } from '../../../utils'
|
||||
import Column from '../../Column'
|
||||
@@ -106,6 +108,7 @@ function CurrencyRow({
|
||||
otherSelected,
|
||||
style,
|
||||
showCurrencyAmount,
|
||||
eventProperties,
|
||||
}: {
|
||||
currency: Currency
|
||||
onSelect: () => void
|
||||
@@ -113,8 +116,9 @@ function CurrencyRow({
|
||||
otherSelected: boolean
|
||||
style: CSSProperties
|
||||
showCurrencyAmount?: boolean
|
||||
eventProperties: Record<string, unknown>
|
||||
}) {
|
||||
const { account } = useActiveWeb3React()
|
||||
const { account } = useWeb3React()
|
||||
const key = currencyKey(currency)
|
||||
const selectedTokenList = useCombinedActiveList()
|
||||
const isOnSelectedList = isTokenOnList(selectedTokenList, currency.isToken ? currency : undefined)
|
||||
@@ -123,35 +127,42 @@ 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}
|
||||
<TraceEvent
|
||||
events={[Event.onClick, Event.onKeyPress]}
|
||||
name={EventName.TOKEN_SELECTED}
|
||||
properties={{ is_imported_by_user: customAdded, ...eventProperties }}
|
||||
element={ElementName.TOKEN_SELECTOR_ROW}
|
||||
>
|
||||
<CurrencyLogo currency={currency} size={'24px'} />
|
||||
<Column>
|
||||
<Text title={currency.name} fontWeight={500}>
|
||||
{currency.symbol}
|
||||
</Text>
|
||||
<ThemedText.DarkGray ml="0px" fontSize={'12px'} fontWeight={300}>
|
||||
{!currency.isNative && !isOnSelectedList && customAdded ? (
|
||||
<Trans>{currency.name} • Added by user</Trans>
|
||||
) : (
|
||||
currency.name
|
||||
)}
|
||||
</ThemedText.DarkGray>
|
||||
</Column>
|
||||
<TokenTags currency={currency} />
|
||||
{showCurrencyAmount && (
|
||||
<RowFixed style={{ justifySelf: 'flex-end' }}>
|
||||
{balance ? <Balance balance={balance} /> : account ? <Loader /> : null}
|
||||
</RowFixed>
|
||||
)}
|
||||
</MenuItem>
|
||||
<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}
|
||||
>
|
||||
<CurrencyLogo currency={currency} size={'24px'} />
|
||||
<Column>
|
||||
<Text title={currency.name} fontWeight={500}>
|
||||
{currency.symbol}
|
||||
</Text>
|
||||
<ThemedText.DarkGray ml="0px" fontSize={'12px'} fontWeight={300}>
|
||||
{!currency.isNative && !isOnSelectedList && customAdded ? (
|
||||
<Trans>{currency.name} • Added by user</Trans>
|
||||
) : (
|
||||
currency.name
|
||||
)}
|
||||
</ThemedText.DarkGray>
|
||||
</Column>
|
||||
<TokenTags currency={currency} />
|
||||
{showCurrencyAmount && (
|
||||
<RowFixed style={{ justifySelf: 'flex-end' }}>
|
||||
{balance ? <Balance balance={balance} /> : account ? <Loader /> : null}
|
||||
</RowFixed>
|
||||
)}
|
||||
</MenuItem>
|
||||
</TraceEvent>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -186,6 +197,31 @@ function BreakLineComponent({ style }: { style: CSSProperties }) {
|
||||
)
|
||||
}
|
||||
|
||||
interface TokenRowProps {
|
||||
data: Array<Currency | BreakLine>
|
||||
index: number
|
||||
style: CSSProperties
|
||||
}
|
||||
|
||||
const formatAnalyticsEventProperties = (
|
||||
token: Token,
|
||||
index: number,
|
||||
data: any[],
|
||||
searchQuery: string,
|
||||
isAddressSearch: string | false
|
||||
) => ({
|
||||
token_symbol: token?.symbol,
|
||||
token_address: token?.address,
|
||||
is_suggested_token: false,
|
||||
is_selected_from_list: true,
|
||||
scroll_position: '',
|
||||
token_list_index: index,
|
||||
token_list_length: data.length,
|
||||
...(isAddressSearch === false
|
||||
? { search_token_symbol_input: searchQuery }
|
||||
: { search_token_address_input: isAddressSearch }),
|
||||
})
|
||||
|
||||
export default function CurrencyList({
|
||||
height,
|
||||
currencies,
|
||||
@@ -198,6 +234,8 @@ export default function CurrencyList({
|
||||
setImportToken,
|
||||
showCurrencyAmount,
|
||||
isLoading,
|
||||
searchQuery,
|
||||
isAddressSearch,
|
||||
}: {
|
||||
height: number
|
||||
currencies: Currency[]
|
||||
@@ -210,6 +248,8 @@ export default function CurrencyList({
|
||||
setImportToken: (token: Token) => void
|
||||
showCurrencyAmount?: boolean
|
||||
isLoading: boolean
|
||||
searchQuery: string
|
||||
isAddressSearch: string | false
|
||||
}) {
|
||||
const itemData: (Currency | BreakLine)[] = useMemo(() => {
|
||||
if (otherListTokens && otherListTokens?.length > 0) {
|
||||
@@ -219,7 +259,7 @@ export default function CurrencyList({
|
||||
}, [currencies, otherListTokens])
|
||||
|
||||
const Row = useCallback(
|
||||
function TokenRow({ data, index, style }) {
|
||||
function TokenRow({ data, index, style }: TokenRowProps) {
|
||||
const row: Currency | BreakLine = data[index]
|
||||
|
||||
if (isBreakLine(row)) {
|
||||
@@ -257,6 +297,7 @@ export default function CurrencyList({
|
||||
onSelect={handleSelect}
|
||||
otherSelected={otherSelected}
|
||||
showCurrencyAmount={showCurrencyAmount}
|
||||
eventProperties={formatAnalyticsEventProperties(token, index, data, searchQuery, isAddressSearch)}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
@@ -272,6 +313,8 @@ export default function CurrencyList({
|
||||
showImportView,
|
||||
showCurrencyAmount,
|
||||
isLoading,
|
||||
isAddressSearch,
|
||||
searchQuery,
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { Currency, Token } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { EventName, ModalName } from 'components/AmplitudeAnalytics/constants'
|
||||
import { Trace } from 'components/AmplitudeAnalytics/Trace'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import useDebounce from 'hooks/useDebounce'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
@@ -10,12 +12,12 @@ import useToggle from 'hooks/useToggle'
|
||||
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
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 { ChangeEvent, KeyboardEvent, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Edit } from 'react-feather'
|
||||
import AutoSizer from 'react-virtualized-auto-sizer'
|
||||
import { FixedSizeList } from 'react-window'
|
||||
import { Text } from 'rebass'
|
||||
import { useAllTokenBalances } from 'state/wallet/hooks'
|
||||
import { useAllTokenBalances } from 'state/connection/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { useAllTokens, useIsUserAddedToken, useSearchInactiveTokenLists, useToken } from '../../hooks/Tokens'
|
||||
@@ -71,7 +73,7 @@ export function CurrencySearch({
|
||||
showImportView,
|
||||
setImportToken,
|
||||
}: CurrencySearchProps) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId } = useWeb3React()
|
||||
const theme = useTheme()
|
||||
|
||||
const [tokenLoaderTimerElapsed, setTokenLoaderTimerElapsed] = useState(false)
|
||||
@@ -116,11 +118,15 @@ export function CurrencySearch({
|
||||
const native = useNativeCurrency()
|
||||
|
||||
const filteredSortedTokensWithETH: Currency[] = useMemo(() => {
|
||||
if (!native) return filteredSortedTokens
|
||||
// Use Celo ERC20 Implementation and exclude the native asset
|
||||
if (!native) {
|
||||
return filteredSortedTokens
|
||||
}
|
||||
|
||||
const s = debouncedQuery.toLowerCase().trim()
|
||||
if (native.symbol?.toLowerCase()?.indexOf(s) !== -1) {
|
||||
return native ? [native, ...filteredSortedTokens] : filteredSortedTokens
|
||||
// Always bump the native token to the top of the list.
|
||||
return native ? [native, ...filteredSortedTokens.filter((t) => !t.equals(native))] : filteredSortedTokens
|
||||
}
|
||||
return filteredSortedTokens
|
||||
}, [debouncedQuery, native, filteredSortedTokens])
|
||||
@@ -140,7 +146,7 @@ export function CurrencySearch({
|
||||
|
||||
// manage focus on modal show
|
||||
const inputRef = useRef<HTMLInputElement>()
|
||||
const handleInput = useCallback((event) => {
|
||||
const handleInput = useCallback((event: ChangeEvent<HTMLInputElement>) => {
|
||||
const input = event.target.value
|
||||
const checksummedInput = isAddress(input)
|
||||
setSearchQuery(checksummedInput || input)
|
||||
@@ -185,76 +191,86 @@ export function CurrencySearch({
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<ContentWrapper>
|
||||
<PaddedColumn gap="16px">
|
||||
<RowBetween>
|
||||
<Text fontWeight={500} fontSize={16}>
|
||||
<Trans>Select a token</Trans>
|
||||
</Text>
|
||||
<CloseIcon onClick={onDismiss} />
|
||||
</RowBetween>
|
||||
<Row>
|
||||
<SearchInput
|
||||
type="text"
|
||||
id="token-search-input"
|
||||
placeholder={t`Search name or paste address`}
|
||||
autoComplete="off"
|
||||
value={searchQuery}
|
||||
ref={inputRef as RefObject<HTMLInputElement>}
|
||||
onChange={handleInput}
|
||||
onKeyDown={handleEnter}
|
||||
/>
|
||||
</Row>
|
||||
{showCommonBases && (
|
||||
<CommonBases chainId={chainId} onSelect={handleCurrencySelect} selectedCurrency={selectedCurrency} />
|
||||
<Trace name={EventName.TOKEN_SELECTOR_OPENED} modal={ModalName.TOKEN_SELECTOR} shouldLogImpression={true}>
|
||||
<ContentWrapper>
|
||||
<PaddedColumn gap="16px">
|
||||
<RowBetween>
|
||||
<Text fontWeight={500} fontSize={16}>
|
||||
<Trans>Select a token</Trans>
|
||||
</Text>
|
||||
<CloseIcon onClick={onDismiss} />
|
||||
</RowBetween>
|
||||
<Row>
|
||||
<SearchInput
|
||||
type="text"
|
||||
id="token-search-input"
|
||||
placeholder={t`Search name or paste address`}
|
||||
autoComplete="off"
|
||||
value={searchQuery}
|
||||
ref={inputRef as RefObject<HTMLInputElement>}
|
||||
onChange={handleInput}
|
||||
onKeyDown={handleEnter}
|
||||
/>
|
||||
</Row>
|
||||
{showCommonBases && (
|
||||
<CommonBases
|
||||
chainId={chainId}
|
||||
onSelect={handleCurrencySelect}
|
||||
selectedCurrency={selectedCurrency}
|
||||
searchQuery={searchQuery}
|
||||
isAddressSearch={isAddressSearch}
|
||||
/>
|
||||
)}
|
||||
</PaddedColumn>
|
||||
<Separator />
|
||||
{searchToken && !searchTokenIsAdded ? (
|
||||
<Column style={{ padding: '20px 0', height: '100%' }}>
|
||||
<ImportRow token={searchToken} showImportView={showImportView} setImportToken={setImportToken} />
|
||||
</Column>
|
||||
) : filteredSortedTokens?.length > 0 || filteredInactiveTokens?.length > 0 ? (
|
||||
<div style={{ flex: '1' }}>
|
||||
<AutoSizer disableWidth>
|
||||
{({ height }) => (
|
||||
<CurrencyList
|
||||
height={height}
|
||||
currencies={disableNonToken ? filteredSortedTokens : filteredSortedTokensWithETH}
|
||||
otherListTokens={filteredInactiveTokens}
|
||||
onCurrencySelect={handleCurrencySelect}
|
||||
otherCurrency={otherSelectedCurrency}
|
||||
selectedCurrency={selectedCurrency}
|
||||
fixedListRef={fixedList}
|
||||
showImportView={showImportView}
|
||||
setImportToken={setImportToken}
|
||||
showCurrencyAmount={showCurrencyAmount}
|
||||
isLoading={balancesIsLoading && !tokenLoaderTimerElapsed}
|
||||
searchQuery={searchQuery}
|
||||
isAddressSearch={isAddressSearch}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
) : (
|
||||
<Column style={{ padding: '20px', height: '100%' }}>
|
||||
<ThemedText.Main color={theme.text3} textAlign="center" mb="20px">
|
||||
<Trans>No results found.</Trans>
|
||||
</ThemedText.Main>
|
||||
</Column>
|
||||
)}
|
||||
</PaddedColumn>
|
||||
<Separator />
|
||||
{searchToken && !searchTokenIsAdded ? (
|
||||
<Column style={{ padding: '20px 0', height: '100%' }}>
|
||||
<ImportRow token={searchToken} showImportView={showImportView} setImportToken={setImportToken} />
|
||||
</Column>
|
||||
) : filteredSortedTokens?.length > 0 || filteredInactiveTokens?.length > 0 ? (
|
||||
<div style={{ flex: '1' }}>
|
||||
<AutoSizer disableWidth>
|
||||
{({ height }) => (
|
||||
<CurrencyList
|
||||
height={height}
|
||||
currencies={disableNonToken ? filteredSortedTokens : filteredSortedTokensWithETH}
|
||||
otherListTokens={filteredInactiveTokens}
|
||||
onCurrencySelect={handleCurrencySelect}
|
||||
otherCurrency={otherSelectedCurrency}
|
||||
selectedCurrency={selectedCurrency}
|
||||
fixedListRef={fixedList}
|
||||
showImportView={showImportView}
|
||||
setImportToken={setImportToken}
|
||||
showCurrencyAmount={showCurrencyAmount}
|
||||
isLoading={balancesIsLoading && !tokenLoaderTimerElapsed}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
) : (
|
||||
<Column style={{ padding: '20px', height: '100%' }}>
|
||||
<ThemedText.Main color={theme.text3} textAlign="center" mb="20px">
|
||||
<Trans>No results found.</Trans>
|
||||
</ThemedText.Main>
|
||||
</Column>
|
||||
)}
|
||||
<Footer>
|
||||
<Row justify="center">
|
||||
<ButtonText onClick={showManageView} color={theme.primary1} className="list-token-manage-button">
|
||||
<RowFixed>
|
||||
<IconWrapper size="16px" marginRight="6px" stroke={theme.primaryText1}>
|
||||
<Edit />
|
||||
</IconWrapper>
|
||||
<ThemedText.Main color={theme.primaryText1}>
|
||||
<Trans>Manage Token Lists</Trans>
|
||||
</ThemedText.Main>
|
||||
</RowFixed>
|
||||
</ButtonText>
|
||||
</Row>
|
||||
</Footer>
|
||||
</ContentWrapper>
|
||||
<Footer>
|
||||
<Row justify="center">
|
||||
<ButtonText onClick={showManageView} color={theme.primary1} className="list-token-manage-button">
|
||||
<RowFixed>
|
||||
<IconWrapper size="16px" marginRight="6px" stroke={theme.primaryText1}>
|
||||
<Edit />
|
||||
</IconWrapper>
|
||||
<ThemedText.Main color={theme.primaryText1}>
|
||||
<Trans>Manage Token Lists</Trans>
|
||||
</ThemedText.Main>
|
||||
</RowFixed>
|
||||
</ButtonText>
|
||||
</Row>
|
||||
</Footer>
|
||||
</ContentWrapper>
|
||||
</Trace>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Plural, Trans } from '@lingui/macro'
|
||||
import { Currency, Token } from '@uniswap/sdk-core'
|
||||
import { TokenList } from '@uniswap/token-lists'
|
||||
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
|
||||
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
|
||||
import { ButtonPrimary } from 'components/Button'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { RowBetween } from 'components/Row'
|
||||
@@ -30,6 +32,12 @@ interface ImportProps {
|
||||
handleCurrencySelect?: (currency: Currency) => void
|
||||
}
|
||||
|
||||
const formatAnalyticsEventProperties = (tokens: Token[]) => ({
|
||||
token_symbols: tokens.map((token) => token?.symbol),
|
||||
token_addresses: tokens.map((token) => token?.address),
|
||||
token_chain_ids: tokens.map((token) => token?.chainId),
|
||||
})
|
||||
|
||||
export function ImportToken(props: ImportProps) {
|
||||
const { tokens, list, onBack, onDismiss, handleCurrencySelect } = props
|
||||
const theme = useTheme()
|
||||
@@ -42,6 +50,7 @@ export function ImportToken(props: ImportProps) {
|
||||
if (intersection.size > 0) {
|
||||
return <BlockedToken onBack={onBack} onDismiss={onDismiss} blockedTokens={Array.from(intersection)} />
|
||||
}
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<PaddedColumn gap="14px" style={{ width: '100%', flex: '1 1' }}>
|
||||
@@ -67,18 +76,25 @@ export function ImportToken(props: ImportProps) {
|
||||
{tokens.map((token) => (
|
||||
<TokenImportCard token={token} list={list} key={'import' + token.address} />
|
||||
))}
|
||||
<ButtonPrimary
|
||||
altDisabledStyle={true}
|
||||
$borderRadius="20px"
|
||||
padding="10px 1rem"
|
||||
onClick={() => {
|
||||
tokens.map((token) => addToken(token))
|
||||
handleCurrencySelect && handleCurrencySelect(tokens[0])
|
||||
}}
|
||||
className=".token-dismiss-button"
|
||||
<TraceEvent
|
||||
events={[Event.onClick]}
|
||||
name={EventName.TOKEN_IMPORTED}
|
||||
properties={formatAnalyticsEventProperties(tokens)}
|
||||
element={ElementName.IMPORT_TOKEN_BUTTON}
|
||||
>
|
||||
<Trans>Import</Trans>
|
||||
</ButtonPrimary>
|
||||
<ButtonPrimary
|
||||
altDisabledStyle={true}
|
||||
$borderRadius="20px"
|
||||
padding="10px 1rem"
|
||||
onClick={() => {
|
||||
tokens.map((token) => addToken(token))
|
||||
handleCurrencySelect && handleCurrencySelect(tokens[0])
|
||||
}}
|
||||
className=".token-dismiss-button"
|
||||
>
|
||||
<Trans>Import</Trans>
|
||||
</ButtonPrimary>
|
||||
</TraceEvent>
|
||||
</AutoColumn>
|
||||
</Wrapper>
|
||||
)
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { TokenList } from '@uniswap/token-lists'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import Card from 'components/Card'
|
||||
import { UNSUPPORTED_LIST_URLS } from 'constants/lists'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useListColor } from 'hooks/useColor'
|
||||
import parseENSAddress from 'lib/utils/parseENSAddress'
|
||||
import uriToHttp from 'lib/utils/uriToHttp'
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { ChangeEvent, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { CheckCircle, Settings } from 'react-feather'
|
||||
import { usePopper } from 'react-popper'
|
||||
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||
@@ -95,7 +95,7 @@ function listUrlRowHTMLId(listUrl: string) {
|
||||
}
|
||||
|
||||
const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId } = useWeb3React()
|
||||
const listsByUrl = useAppSelector((state) => state.lists.byUrl)
|
||||
const dispatch = useAppDispatch()
|
||||
const { current: list, pendingUpdate: pending } = listsByUrl[listUrl]
|
||||
@@ -242,7 +242,7 @@ export function ManageLists({
|
||||
setImportList: (list: TokenList) => void
|
||||
setListUrl: (url: string) => void
|
||||
}) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId } = useWeb3React()
|
||||
const theme = useTheme()
|
||||
|
||||
const [listUrlInput, setListUrlInput] = useState<string>('')
|
||||
@@ -266,7 +266,7 @@ export function ManageLists({
|
||||
// sort by active but only if not visible
|
||||
const activeListUrls = useActiveListUrls()
|
||||
|
||||
const handleInput = useCallback((e) => {
|
||||
const handleInput = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
setListUrlInput(e.target.value)
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Token } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import Card from 'components/Card'
|
||||
import Column from 'components/Column'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import Row, { RowBetween, RowFixed } from 'components/Row'
|
||||
import { useToken } from 'hooks/Tokens'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { RefObject, useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { ChangeEvent, RefObject, useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { useRemoveUserAddedToken, useUserAddedTokens } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ButtonText, ExternalLink, ExternalLinkIcon, ThemedText, TrashIcon } from 'theme'
|
||||
@@ -44,14 +44,14 @@ export default function ManageTokens({
|
||||
setModalView: (view: CurrencyModalView) => void
|
||||
setImportToken: (token: Token) => void
|
||||
}) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId } = useWeb3React()
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState<string>('')
|
||||
const theme = useTheme()
|
||||
|
||||
// manage focus on modal show
|
||||
const inputRef = useRef<HTMLInputElement>()
|
||||
const handleInput = useCallback((event) => {
|
||||
const handleInput = useCallback((event: ChangeEvent<HTMLInputElement>) => {
|
||||
const input = event.target.value
|
||||
const checksummedInput = isAddress(input)
|
||||
setSearchQuery(checksummedInput || input)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Token } from '@uniswap/sdk-core'
|
||||
import { TokenList } from '@uniswap/token-lists'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import Card from 'components/Card'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import ListLogo from 'components/ListLogo'
|
||||
import { RowFixed } from 'components/Row'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { transparentize } from 'polished'
|
||||
import { AlertCircle } from 'react-feather'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
@@ -33,7 +33,7 @@ interface TokenImportCardProps {
|
||||
}
|
||||
const TokenImportCard = ({ list, token }: TokenImportCardProps) => {
|
||||
const theme = useTheme()
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId } = useWeb3React()
|
||||
return (
|
||||
<Card backgroundColor={theme.bg2} padding="2rem">
|
||||
<AutoColumn gap="10px" justify="center">
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { AUTO_ROUTER_SUPPORTED_CHAINS } from 'lib/hooks/routing/clientSideSmartOrderRouter'
|
||||
import { isSupportedChainId } from 'lib/hooks/routing/clientSideSmartOrderRouter'
|
||||
import { useContext, useRef, useState } from 'react'
|
||||
import { Settings, X } from 'react-feather'
|
||||
import { Text } from 'rebass'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
|
||||
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
|
||||
import { useModalOpen, useToggleSettingsMenu } from '../../state/application/hooks'
|
||||
import { useModalIsOpen, useToggleSettingsMenu } from '../../state/application/hooks'
|
||||
import { ApplicationModal } from '../../state/application/reducer'
|
||||
import { useClientSideRouter, useExpertModeManager } from '../../state/user/hooks'
|
||||
import { ThemedText } from '../../theme'
|
||||
@@ -119,10 +119,10 @@ const ModalContentWrapper = styled.div`
|
||||
`
|
||||
|
||||
export default function SettingsTab({ placeholderSlippage }: { placeholderSlippage: Percent }) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId } = useWeb3React()
|
||||
|
||||
const node = useRef<HTMLDivElement>()
|
||||
const open = useModalOpen(ApplicationModal.SETTINGS)
|
||||
const open = useModalIsOpen(ApplicationModal.SETTINGS)
|
||||
const toggle = useToggleSettingsMenu()
|
||||
|
||||
const theme = useContext(ThemeContext)
|
||||
@@ -199,7 +199,7 @@ export default function SettingsTab({ placeholderSlippage }: { placeholderSlippa
|
||||
<Text fontWeight={600} fontSize={14}>
|
||||
<Trans>Interface Settings</Trans>
|
||||
</Text>
|
||||
{chainId && AUTO_ROUTER_SUPPORTED_CHAINS.includes(chainId) && (
|
||||
{isSupportedChainId(chainId) && (
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<ThemedText.Black fontWeight={400} fontSize={14} color={theme.text2}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback } from 'react'
|
||||
import { ChangeEvent, useCallback } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const StyledRangeInput = styled.input<{ size: number }>`
|
||||
@@ -106,7 +106,7 @@ export default function Slider({
|
||||
...rest
|
||||
}: InputSliderProps) {
|
||||
const changeCallback = useCallback(
|
||||
(e) => {
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange(parseInt(e.target.value))
|
||||
},
|
||||
[onChange]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { memo, useCallback, useRef } from 'react'
|
||||
import React, { ChangeEvent, memo, useCallback, useRef } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const Input = styled.input<{ error?: boolean; fontSize?: string }>`
|
||||
@@ -77,7 +77,7 @@ export const TextInput = ({
|
||||
fontSize: string
|
||||
}) => {
|
||||
const handleInput = useCallback(
|
||||
(event) => {
|
||||
(event: ChangeEvent<HTMLInputElement>) => {
|
||||
onUserInput(event.target.value)
|
||||
},
|
||||
[onUserInput]
|
||||
@@ -117,7 +117,7 @@ export const ResizingTextArea = memo(
|
||||
const inputRef = useRef<HTMLTextAreaElement>(document.createElement('textarea'))
|
||||
|
||||
const handleInput = useCallback(
|
||||
(event) => {
|
||||
(event: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
inputRef.current.style.height = 'auto'
|
||||
inputRef.current.style.height = inputRef.current.scrollHeight + 'px'
|
||||
onUserInput(event.target.value)
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import AddressClaimModal from 'components/claim/AddressClaimModal'
|
||||
import ConnectedAccountBlocked from 'components/ConnectedAccountBlocked'
|
||||
import useAccountRiskCheck from 'hooks/useAccountRiskCheck'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useModalOpen, useToggleModal } from 'state/application/hooks'
|
||||
import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
|
||||
export default function TopLevelModals() {
|
||||
const addressClaimOpen = useModalOpen(ApplicationModal.ADDRESS_CLAIM)
|
||||
const addressClaimOpen = useModalIsOpen(ApplicationModal.ADDRESS_CLAIM)
|
||||
const addressClaimToggle = useToggleModal(ApplicationModal.ADDRESS_CLAIM)
|
||||
|
||||
const blockedAccountModalOpen = useModalOpen(ApplicationModal.BLOCKED_ACCOUNT)
|
||||
const { account } = useActiveWeb3React()
|
||||
const blockedAccountModalOpen = useModalIsOpen(ApplicationModal.BLOCKED_ACCOUNT)
|
||||
const { account } = useWeb3React()
|
||||
|
||||
useAccountRiskCheck(account)
|
||||
const open = Boolean(blockedAccountModalOpen && account)
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
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 { getChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedL2ChainId } from 'constants/chains'
|
||||
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 { isL2ChainId } from 'utils/chains'
|
||||
|
||||
import Circle from '../../assets/images/blue-loader.svg'
|
||||
import { ExternalLink } from '../../theme'
|
||||
@@ -96,7 +97,7 @@ function TransactionSubmittedContent({
|
||||
}) {
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
const { connector } = useActiveWeb3React()
|
||||
const { connector } = useWeb3React()
|
||||
|
||||
const token = currencyToAdd?.wrapped
|
||||
const logoURL = useCurrencyLogoURIs(token)[0]
|
||||
@@ -182,7 +183,7 @@ export function ConfirmationModalContent({
|
||||
<Text fontWeight={500} fontSize={16}>
|
||||
{title}
|
||||
</Text>
|
||||
<CloseIcon onClick={onDismiss} />
|
||||
<CloseIcon onClick={onDismiss} data-cy="confirmation-close-icon" />
|
||||
</RowBetween>
|
||||
{topContent()}
|
||||
</Section>
|
||||
@@ -232,7 +233,7 @@ function L2Content({
|
||||
}: {
|
||||
onDismiss: () => void
|
||||
hash: string | undefined
|
||||
chainId: number
|
||||
chainId: SupportedL2ChainId
|
||||
currencyToAdd?: Currency | undefined
|
||||
pendingText: ReactNode
|
||||
inline?: boolean // not in modal
|
||||
@@ -248,7 +249,7 @@ function L2Content({
|
||||
? (transaction.confirmedTime - transaction.addedTime) / 1000
|
||||
: undefined
|
||||
|
||||
const info = CHAIN_INFO[chainId as SupportedL2ChainId]
|
||||
const info = getChainInfo(chainId)
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
@@ -342,16 +343,14 @@ export default function TransactionConfirmationModal({
|
||||
content,
|
||||
currencyToAdd,
|
||||
}: ConfirmationModalProps) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
|
||||
const isL2 = Boolean(chainId && L2_CHAIN_IDS.includes(chainId))
|
||||
const { chainId } = useWeb3React()
|
||||
|
||||
if (!chainId) return null
|
||||
|
||||
// confirmation screen
|
||||
return (
|
||||
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={90}>
|
||||
{isL2 && (hash || attemptingTxn) ? (
|
||||
{isL2ChainId(chainId) && (hash || attemptingTxn) ? (
|
||||
<L2Content chainId={chainId} hash={hash} onDismiss={onDismiss} pendingText={pendingText} />
|
||||
) : attemptingTxn ? (
|
||||
<ConfirmationPendingContent onDismiss={onDismiss} pendingText={pendingText} />
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { L2_CHAIN_IDS } from 'constants/chains'
|
||||
import { DEFAULT_DEADLINE_FROM_NOW } from 'constants/misc'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import ms from 'ms.macro'
|
||||
import { darken } from 'polished'
|
||||
import { useContext, useState } from 'react'
|
||||
@@ -97,7 +97,7 @@ interface TransactionSettingsProps {
|
||||
const THREE_DAYS_IN_SECONDS = ms`3 days` / 1000
|
||||
|
||||
export default function TransactionSettings({ placeholderSlippage }: TransactionSettingsProps) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId } = useWeb3React()
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
const userSlippageTolerance = useUserSlippageTolerance()
|
||||
|
||||
36
src/components/WalletModal/CoinbaseWalletOption.tsx
Normal file
36
src/components/WalletModal/CoinbaseWalletOption.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Connector } from '@web3-react/types'
|
||||
import COINBASE_ICON_URL from 'assets/images/coinbaseWalletIcon.svg'
|
||||
import { coinbaseWalletConnection, ConnectionType } from 'connection'
|
||||
import { getConnectionName } from 'connection/utils'
|
||||
|
||||
import Option from './Option'
|
||||
|
||||
const BASE_PROPS = {
|
||||
color: '#315CF5',
|
||||
icon: COINBASE_ICON_URL,
|
||||
id: 'coinbase-wallet',
|
||||
}
|
||||
|
||||
export function OpenCoinbaseWalletOption() {
|
||||
const isActive = coinbaseWalletConnection.hooks.useIsActive()
|
||||
return (
|
||||
<Option
|
||||
{...BASE_PROPS}
|
||||
isActive={isActive}
|
||||
link="https://go.cb-w.com/mtUDhEZPy1"
|
||||
header="Open in Coinbase Wallet"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function CoinbaseWalletOption({ tryActivation }: { tryActivation: (connector: Connector) => void }) {
|
||||
const isActive = coinbaseWalletConnection.hooks.useIsActive()
|
||||
return (
|
||||
<Option
|
||||
{...BASE_PROPS}
|
||||
isActive={isActive}
|
||||
onClick={() => tryActivation(coinbaseWalletConnection.connector)}
|
||||
header={getConnectionName(ConnectionType.COINBASE_WALLET)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
24
src/components/WalletModal/FortmaticOption.tsx
Normal file
24
src/components/WalletModal/FortmaticOption.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Connector } from '@web3-react/types'
|
||||
import FORTMATIC_ICON_URL from 'assets/images/fortmaticIcon.png'
|
||||
import { ConnectionType, fortmaticConnection } from 'connection'
|
||||
import { getConnectionName } from 'connection/utils'
|
||||
|
||||
import Option from './Option'
|
||||
|
||||
const BASE_PROPS = {
|
||||
color: '#6748FF',
|
||||
icon: FORTMATIC_ICON_URL,
|
||||
id: 'fortmatic',
|
||||
}
|
||||
|
||||
export function FortmaticOption({ tryActivation }: { tryActivation: (connector: Connector) => void }) {
|
||||
const isActive = fortmaticConnection.hooks.useIsActive()
|
||||
return (
|
||||
<Option
|
||||
{...BASE_PROPS}
|
||||
isActive={isActive}
|
||||
onClick={() => tryActivation(fortmaticConnection.connector)}
|
||||
header={getConnectionName(ConnectionType.FORTMATIC)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
48
src/components/WalletModal/InjectedOption.tsx
Normal file
48
src/components/WalletModal/InjectedOption.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Connector } from '@web3-react/types'
|
||||
import INJECTED_ICON_URL from 'assets/images/arrow-right.svg'
|
||||
import METAMASK_ICON_URL from 'assets/images/metamask.png'
|
||||
import { ConnectionType, injectedConnection } from 'connection'
|
||||
import { getConnectionName } from 'connection/utils'
|
||||
|
||||
import Option from './Option'
|
||||
|
||||
const INJECTED_PROPS = {
|
||||
color: '#010101',
|
||||
icon: INJECTED_ICON_URL,
|
||||
id: 'injected',
|
||||
}
|
||||
|
||||
const METAMASK_PROPS = {
|
||||
color: '#E8831D',
|
||||
icon: METAMASK_ICON_URL,
|
||||
id: 'metamask',
|
||||
}
|
||||
|
||||
export function InstallMetaMaskOption() {
|
||||
return <Option {...METAMASK_PROPS} header={<Trans>Install MetaMask</Trans>} link={'https://metamask.io/'} />
|
||||
}
|
||||
|
||||
export function MetaMaskOption({ tryActivation }: { tryActivation: (connector: Connector) => void }) {
|
||||
const isActive = injectedConnection.hooks.useIsActive()
|
||||
return (
|
||||
<Option
|
||||
{...METAMASK_PROPS}
|
||||
isActive={isActive}
|
||||
header={getConnectionName(ConnectionType.INJECTED, true)}
|
||||
onClick={() => tryActivation(injectedConnection.connector)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function InjectedOption({ tryActivation }: { tryActivation: (connector: Connector) => void }) {
|
||||
const isActive = injectedConnection.hooks.useIsActive()
|
||||
return (
|
||||
<Option
|
||||
{...INJECTED_PROPS}
|
||||
isActive={isActive}
|
||||
header={getConnectionName(ConnectionType.INJECTED, false)}
|
||||
onClick={() => tryActivation(injectedConnection.connector)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
|
||||
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
|
||||
import React from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { ExternalLink } from '../../theme'
|
||||
|
||||
const InfoCard = styled.button<{ active?: boolean }>`
|
||||
background-color: ${({ theme, active }) => (active ? theme.bg3 : theme.bg2)};
|
||||
const InfoCard = styled.button<{ isActive?: boolean }>`
|
||||
background-color: ${({ theme, isActive }) => (isActive ? theme.bg3 : theme.bg2)};
|
||||
padding: 1rem;
|
||||
outline: none;
|
||||
border: 1px solid;
|
||||
@@ -13,7 +15,7 @@ const InfoCard = styled.button<{ active?: boolean }>`
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 1px ${({ theme }) => theme.primary1};
|
||||
}
|
||||
border-color: ${({ theme, active }) => (active ? 'transparent' : theme.bg3)};
|
||||
border-color: ${({ theme, isActive }) => (isActive ? 'transparent' : theme.bg3)};
|
||||
`
|
||||
|
||||
const OptionCard = styled(InfoCard as any)`
|
||||
@@ -95,9 +97,9 @@ export default function Option({
|
||||
onClick = null,
|
||||
color,
|
||||
header,
|
||||
subheader = null,
|
||||
subheader,
|
||||
icon,
|
||||
active = false,
|
||||
isActive = false,
|
||||
id,
|
||||
}: {
|
||||
link?: string | null
|
||||
@@ -106,32 +108,45 @@ export default function Option({
|
||||
onClick?: null | (() => void)
|
||||
color: string
|
||||
header: React.ReactNode
|
||||
subheader: React.ReactNode | null
|
||||
subheader?: React.ReactNode
|
||||
icon: string
|
||||
active?: boolean
|
||||
isActive?: boolean
|
||||
id: string
|
||||
}) {
|
||||
const content = (
|
||||
<OptionCardClickable id={id} onClick={onClick} clickable={clickable && !active} active={active}>
|
||||
<OptionCardLeft>
|
||||
<HeaderText color={color}>
|
||||
{active ? (
|
||||
<CircleWrapper>
|
||||
<GreenCircle>
|
||||
<div />
|
||||
</GreenCircle>
|
||||
</CircleWrapper>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
{header}
|
||||
</HeaderText>
|
||||
{subheader && <SubHeader>{subheader}</SubHeader>}
|
||||
</OptionCardLeft>
|
||||
<IconWrapper size={size}>
|
||||
<img src={icon} alt={'Icon'} />
|
||||
</IconWrapper>
|
||||
</OptionCardClickable>
|
||||
<TraceEvent
|
||||
events={[Event.onClick]}
|
||||
name={EventName.WALLET_SELECTED}
|
||||
properties={{ wallet_type: header }}
|
||||
element={ElementName.WALLET_TYPE_OPTION}
|
||||
>
|
||||
<OptionCardClickable
|
||||
id={id}
|
||||
onClick={onClick}
|
||||
clickable={clickable && !isActive}
|
||||
active={isActive}
|
||||
data-testid="wallet-modal-option"
|
||||
>
|
||||
<OptionCardLeft>
|
||||
<HeaderText color={color}>
|
||||
{isActive ? (
|
||||
<CircleWrapper>
|
||||
<GreenCircle>
|
||||
<div />
|
||||
</GreenCircle>
|
||||
</CircleWrapper>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
{header}
|
||||
</HeaderText>
|
||||
{subheader && <SubHeader>{subheader}</SubHeader>}
|
||||
</OptionCardLeft>
|
||||
<IconWrapper size={size}>
|
||||
<img src={icon} alt={'Icon'} />
|
||||
</IconWrapper>
|
||||
</OptionCardClickable>
|
||||
</TraceEvent>
|
||||
)
|
||||
if (link) {
|
||||
return <ExternalLink href={link}>{content}</ExternalLink>
|
||||
|
||||
24
src/components/WalletModal/WalletConnectOption.tsx
Normal file
24
src/components/WalletModal/WalletConnectOption.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Connector } from '@web3-react/types'
|
||||
import WALLET_CONNECT_ICON_URL from 'assets/images/walletConnectIcon.svg'
|
||||
import { ConnectionType, walletConnectConnection } from 'connection'
|
||||
import { getConnectionName } from 'connection/utils'
|
||||
|
||||
import Option from './Option'
|
||||
|
||||
const BASE_PROPS = {
|
||||
color: '#4196FC',
|
||||
icon: WALLET_CONNECT_ICON_URL,
|
||||
id: 'wallet-connect',
|
||||
}
|
||||
|
||||
export function WalletConnectOption({ tryActivation }: { tryActivation: (connector: Connector) => void }) {
|
||||
const isActive = walletConnectConnection.hooks.useIsActive()
|
||||
return (
|
||||
<Option
|
||||
{...BASE_PROPS}
|
||||
isActive={isActive}
|
||||
onClick={() => tryActivation(walletConnectConnection.connector)}
|
||||
header={getConnectionName(ConnectionType.WALLET_CONNECT)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
110
src/components/WalletModal/index.test.tsx
Normal file
110
src/components/WalletModal/index.test.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import * as connectionUtils from 'connection/utils'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
|
||||
import { render, screen } from '../../test-utils'
|
||||
import WalletModal from './index'
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
jest.resetModules()
|
||||
})
|
||||
|
||||
const UserAgentMock = jest.requireMock('utils/userAgent')
|
||||
jest.mock('utils/userAgent', () => ({
|
||||
isMobile: false,
|
||||
}))
|
||||
|
||||
jest.mock('.../../state/application/hooks', () => {
|
||||
return {
|
||||
useModalIsOpen: (_modal: ApplicationModal) => true,
|
||||
useToggleWalletModal: () => {
|
||||
return
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
jest.mock('@web3-react/core', () => {
|
||||
const web3React = jest.requireActual('@web3-react/core')
|
||||
return {
|
||||
useWeb3React: () => ({
|
||||
account: undefined,
|
||||
isActive: false,
|
||||
isActivating: false,
|
||||
connector: undefined,
|
||||
}),
|
||||
...web3React,
|
||||
}
|
||||
})
|
||||
|
||||
it('loads Wallet Modal on desktop', async () => {
|
||||
render(<WalletModal pendingTransactions={[]} confirmedTransactions={[]} />)
|
||||
expect(screen.getByText('Install MetaMask')).toBeInTheDocument()
|
||||
expect(screen.getByText('Coinbase Wallet')).toBeInTheDocument()
|
||||
expect(screen.getByText('WalletConnect')).toBeInTheDocument()
|
||||
expect(screen.getByText('Fortmatic')).toBeInTheDocument()
|
||||
expect(screen.getAllByTestId('wallet-modal-option')).toHaveLength(4)
|
||||
})
|
||||
|
||||
it('loads Wallet Modal on desktop with generic Injected', async () => {
|
||||
jest.spyOn(connectionUtils, 'getIsInjected').mockReturnValue(true)
|
||||
jest.spyOn(connectionUtils, 'getIsMetaMask').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getIsCoinbaseWallet').mockReturnValue(false)
|
||||
|
||||
render(<WalletModal pendingTransactions={[]} confirmedTransactions={[]} />)
|
||||
expect(screen.getByText('Injected')).toBeInTheDocument()
|
||||
expect(screen.getByText('Coinbase Wallet')).toBeInTheDocument()
|
||||
expect(screen.getByText('WalletConnect')).toBeInTheDocument()
|
||||
expect(screen.getByText('Fortmatic')).toBeInTheDocument()
|
||||
expect(screen.getAllByTestId('wallet-modal-option')).toHaveLength(4)
|
||||
})
|
||||
|
||||
it('loads Wallet Modal on desktop with MetaMask installed', async () => {
|
||||
jest.spyOn(connectionUtils, 'getIsInjected').mockReturnValue(true)
|
||||
jest.spyOn(connectionUtils, 'getIsMetaMask').mockReturnValue(true)
|
||||
jest.spyOn(connectionUtils, 'getIsCoinbaseWallet').mockReturnValue(false)
|
||||
|
||||
render(<WalletModal pendingTransactions={[]} confirmedTransactions={[]} />)
|
||||
expect(screen.getByText('MetaMask')).toBeInTheDocument()
|
||||
expect(screen.getByText('Coinbase Wallet')).toBeInTheDocument()
|
||||
expect(screen.getByText('WalletConnect')).toBeInTheDocument()
|
||||
expect(screen.getByText('Fortmatic')).toBeInTheDocument()
|
||||
expect(screen.getAllByTestId('wallet-modal-option')).toHaveLength(4)
|
||||
})
|
||||
|
||||
it('loads Wallet Modal on mobile', async () => {
|
||||
UserAgentMock.isMobile = true
|
||||
|
||||
jest.spyOn(connectionUtils, 'getIsInjected').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getIsMetaMask').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getIsCoinbaseWallet').mockReturnValue(false)
|
||||
|
||||
render(<WalletModal pendingTransactions={[]} confirmedTransactions={[]} />)
|
||||
expect(screen.getByText('Open in Coinbase Wallet')).toBeInTheDocument()
|
||||
expect(screen.getByText('WalletConnect')).toBeInTheDocument()
|
||||
expect(screen.getByText('Fortmatic')).toBeInTheDocument()
|
||||
expect(screen.getAllByTestId('wallet-modal-option')).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('loads Wallet Modal on MetaMask browser', async () => {
|
||||
UserAgentMock.isMobile = true
|
||||
|
||||
jest.spyOn(connectionUtils, 'getIsInjected').mockReturnValue(true)
|
||||
jest.spyOn(connectionUtils, 'getIsMetaMask').mockReturnValue(true)
|
||||
jest.spyOn(connectionUtils, 'getIsCoinbaseWallet').mockReturnValue(false)
|
||||
|
||||
render(<WalletModal pendingTransactions={[]} confirmedTransactions={[]} />)
|
||||
expect(screen.getByText('MetaMask')).toBeInTheDocument()
|
||||
expect(screen.getAllByTestId('wallet-modal-option')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('loads Wallet Modal on Coinbase Wallet browser', async () => {
|
||||
UserAgentMock.isMobile = true
|
||||
|
||||
jest.spyOn(connectionUtils, 'getIsInjected').mockReturnValue(true)
|
||||
jest.spyOn(connectionUtils, 'getIsMetaMask').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getIsCoinbaseWallet').mockReturnValue(true)
|
||||
|
||||
render(<WalletModal pendingTransactions={[]} confirmedTransactions={[]} />)
|
||||
expect(screen.getByText('Coinbase Wallet')).toBeInTheDocument()
|
||||
expect(screen.getAllByTestId('wallet-modal-option')).toHaveLength(1)
|
||||
})
|
||||
@@ -1,30 +1,33 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { Connector } from '@web3-react/types'
|
||||
import { sendAnalyticsEvent } from 'components/AmplitudeAnalytics'
|
||||
import { EventName, WALLET_CONNECTION_RESULT } from 'components/AmplitudeAnalytics/constants'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { AutoRow } from 'components/Row'
|
||||
import { ConnectionType } from 'connection'
|
||||
import { getConnection, getConnectionName, getIsCoinbaseWallet, getIsInjected, getIsMetaMask } from 'connection/utils'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { ArrowLeft } from 'react-feather'
|
||||
import { updateConnectionError } from 'state/connection/reducer'
|
||||
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 { isMobile } from 'utils/userAgent'
|
||||
|
||||
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, getWalletForConnector, injected } from '../../connectors'
|
||||
import { SUPPORTED_WALLETS } from '../../constants/wallet'
|
||||
import { useModalOpen, useWalletModalToggle } from '../../state/application/hooks'
|
||||
import { useModalIsOpen, useToggleWalletModal } from '../../state/application/hooks'
|
||||
import { ApplicationModal } from '../../state/application/reducer'
|
||||
import { ExternalLink, ThemedText } from '../../theme'
|
||||
import { isMobile } from '../../utils/userAgent'
|
||||
import AccountDetails from '../AccountDetails'
|
||||
import { LightCard } from '../Card'
|
||||
import Modal from '../Modal'
|
||||
import Option from './Option'
|
||||
import { CoinbaseWalletOption, OpenCoinbaseWalletOption } from './CoinbaseWalletOption'
|
||||
import { FortmaticOption } from './FortmaticOption'
|
||||
import { InjectedOption, InstallMetaMaskOption, MetaMaskOption } from './InjectedOption'
|
||||
import PendingView from './PendingView'
|
||||
import { WalletConnectOption } from './WalletConnectOption'
|
||||
|
||||
const CloseIcon = styled.div`
|
||||
position: absolute;
|
||||
@@ -123,14 +126,15 @@ export default function WalletModal({
|
||||
const { connector, account } = useWeb3React()
|
||||
|
||||
const [walletView, setWalletView] = useState(WALLET_VIEWS.ACCOUNT)
|
||||
const [lastActiveWalletAddress, setLastActiveWalletAddress] = useState<string | undefined>(account)
|
||||
|
||||
const [pendingConnector, setPendingConnector] = useState<Connector | undefined>()
|
||||
const pendingError = useAppSelector((state) =>
|
||||
pendingConnector ? state.wallet.errorByWallet[getWalletForConnector(pendingConnector)] : undefined
|
||||
pendingConnector ? state.connection.errorByConnectionType[getConnection(pendingConnector).type] : undefined
|
||||
)
|
||||
|
||||
const walletModalOpen = useModalOpen(ApplicationModal.WALLET)
|
||||
const toggleWalletModal = useWalletModalToggle()
|
||||
const walletModalOpen = useModalIsOpen(ApplicationModal.WALLET)
|
||||
const toggleWalletModal = useToggleWalletModal()
|
||||
|
||||
const openOptions = useCallback(() => {
|
||||
setWalletView(WALLET_VIEWS.OPTIONS)
|
||||
@@ -144,144 +148,104 @@ export default function WalletModal({
|
||||
|
||||
useEffect(() => {
|
||||
if (pendingConnector && walletView !== WALLET_VIEWS.PENDING) {
|
||||
updateWalletError({ wallet: getWalletForConnector(pendingConnector), error: undefined })
|
||||
updateConnectionError({ connectionType: getConnection(pendingConnector).type, error: undefined })
|
||||
setPendingConnector(undefined)
|
||||
}
|
||||
}, [pendingConnector, walletView])
|
||||
|
||||
// When new wallet is successfully set by the user, trigger logging of Amplitude analytics event.
|
||||
useEffect(() => {
|
||||
if (account && account !== lastActiveWalletAddress) {
|
||||
sendAnalyticsEvent(EventName.WALLET_CONNECT_TXN_COMPLETED, {
|
||||
result: WALLET_CONNECTION_RESULT.SUCCEEDED,
|
||||
wallet_address: account,
|
||||
wallet_type: getConnectionName(getConnection(connector).type, getIsMetaMask()),
|
||||
// TODO(lynnshaoyu): Send correct is_reconnect value after modifying user state.
|
||||
})
|
||||
}
|
||||
setLastActiveWalletAddress(account)
|
||||
}, [lastActiveWalletAddress, account, connector])
|
||||
|
||||
const tryActivation = useCallback(
|
||||
async (connector: Connector) => {
|
||||
const wallet = getWalletForConnector(connector)
|
||||
const connectionType = getConnection(connector).type
|
||||
|
||||
// log selected wallet
|
||||
sendEvent({
|
||||
category: 'Wallet',
|
||||
action: 'Change Wallet',
|
||||
label: wallet,
|
||||
label: connectionType,
|
||||
})
|
||||
|
||||
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) {
|
||||
if (connectionType === ConnectionType.FORTMATIC) {
|
||||
toggleWalletModal()
|
||||
}
|
||||
|
||||
setPendingConnector(connector)
|
||||
setWalletView(WALLET_VIEWS.PENDING)
|
||||
dispatch(updateWalletError({ wallet, error: undefined }))
|
||||
dispatch(updateConnectionError({ connectionType, error: undefined }))
|
||||
|
||||
await connector.activate()
|
||||
|
||||
dispatch(updateSelectedWallet({ wallet }))
|
||||
dispatch(updateSelectedWallet({ wallet: connectionType }))
|
||||
} catch (error) {
|
||||
console.debug(`web3-react connection error: ${error}`)
|
||||
dispatch(updateWalletError({ wallet, error: error.message }))
|
||||
dispatch(updateConnectionError({ connectionType, error: error.message }))
|
||||
|
||||
sendAnalyticsEvent(EventName.WALLET_CONNECT_TXN_COMPLETED, {
|
||||
result: WALLET_CONNECTION_RESULT.FAILED,
|
||||
wallet_type: getConnectionName(connectionType, getIsMetaMask()),
|
||||
})
|
||||
}
|
||||
},
|
||||
[dispatch, toggleWalletModal]
|
||||
)
|
||||
|
||||
// get wallets user can switch too, depending on device/browser
|
||||
function getOptions() {
|
||||
const isMetamask = !!window.ethereum?.isMetaMask
|
||||
const isTally = !!window.ethereum?.isTally
|
||||
return Object.keys(SUPPORTED_WALLETS).map((key) => {
|
||||
const option = SUPPORTED_WALLETS[key]
|
||||
const isActive = option.connector === connector
|
||||
const isInjected = getIsInjected()
|
||||
const isMetaMask = getIsMetaMask()
|
||||
const isCoinbaseWallet = getIsCoinbaseWallet()
|
||||
|
||||
const optionProps = {
|
||||
active: isActive,
|
||||
id: `connect-${key}`,
|
||||
link: option.href,
|
||||
header: option.name,
|
||||
color: option.color,
|
||||
key,
|
||||
icon: option.iconURL,
|
||||
const isCoinbaseWalletBrowser = isMobile && isCoinbaseWallet
|
||||
const isMetaMaskBrowser = isMobile && isMetaMask
|
||||
const isInjectedMobileBrowser = isCoinbaseWalletBrowser || isMetaMaskBrowser
|
||||
|
||||
let injectedOption
|
||||
if (!isInjected) {
|
||||
if (!isMobile) {
|
||||
injectedOption = <InstallMetaMaskOption />
|
||||
}
|
||||
|
||||
// check for mobile options
|
||||
if (isMobile) {
|
||||
if (!window.web3 && !window.ethereum && option.mobile) {
|
||||
return (
|
||||
<Option
|
||||
{...optionProps}
|
||||
onClick={() => {
|
||||
if (!isActive && !option.href && !!option.connector) {
|
||||
tryActivation(option.connector)
|
||||
}
|
||||
}}
|
||||
subheader={null}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return null
|
||||
} else if (!isCoinbaseWallet) {
|
||||
if (isMetaMask) {
|
||||
injectedOption = <MetaMaskOption tryActivation={tryActivation} />
|
||||
} else {
|
||||
injectedOption = <InjectedOption tryActivation={tryActivation} />
|
||||
}
|
||||
}
|
||||
|
||||
// overwrite injected when needed
|
||||
if (option.connector === injected) {
|
||||
// don't show injected if there's no injected provider
|
||||
if (!(window.web3 || window.ethereum)) {
|
||||
if (option.name === 'MetaMask') {
|
||||
return (
|
||||
<Option
|
||||
id={`connect-${key}`}
|
||||
key={key}
|
||||
color={'#E8831D'}
|
||||
header={<Trans>Install Metamask</Trans>}
|
||||
subheader={null}
|
||||
link={'https://metamask.io/'}
|
||||
icon={MetamaskIcon}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return null //dont want to return install twice
|
||||
}
|
||||
}
|
||||
// don't return metamask if injected provider isn't metamask
|
||||
else if (option.name === 'MetaMask' && !isMetamask) {
|
||||
return null
|
||||
}
|
||||
// likewise for generic
|
||||
else if (option.name === 'Injected' && isMetamask) {
|
||||
return null
|
||||
} else if (option.name === 'Injected' && isTally) {
|
||||
return (
|
||||
<Option
|
||||
id={`connect-${key}`}
|
||||
key={key}
|
||||
onClick={() => {
|
||||
option.connector === connector
|
||||
? setWalletView(WALLET_VIEWS.ACCOUNT)
|
||||
: !option.href && option.connector && tryActivation(option.connector)
|
||||
}}
|
||||
color={'#E8831D'}
|
||||
header={<Trans>Tally</Trans>}
|
||||
active={option.connector === connector}
|
||||
subheader={null}
|
||||
link={null}
|
||||
icon={TallyIcon}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
let coinbaseWalletOption
|
||||
if (isMobile && !isInjectedMobileBrowser) {
|
||||
coinbaseWalletOption = <OpenCoinbaseWalletOption />
|
||||
} else if (!isMobile || isCoinbaseWalletBrowser) {
|
||||
coinbaseWalletOption = <CoinbaseWalletOption tryActivation={tryActivation} />
|
||||
}
|
||||
|
||||
// return rest of options
|
||||
return (
|
||||
!isMobile &&
|
||||
!option.mobileOnly && (
|
||||
<Option
|
||||
{...optionProps}
|
||||
onClick={() => {
|
||||
option.connector === connector
|
||||
? setWalletView(WALLET_VIEWS.ACCOUNT)
|
||||
: !option.href && option.connector && tryActivation(option.connector)
|
||||
}}
|
||||
subheader={null} //use option.descriptio to bring back multi-line
|
||||
/>
|
||||
)
|
||||
)
|
||||
})
|
||||
const walletConnectionOption =
|
||||
(!isInjectedMobileBrowser && <WalletConnectOption tryActivation={tryActivation} />) ?? null
|
||||
|
||||
const fortmaticOption = (!isInjectedMobileBrowser && <FortmaticOption tryActivation={tryActivation} />) ?? null
|
||||
|
||||
return (
|
||||
<>
|
||||
{injectedOption}
|
||||
{coinbaseWalletOption}
|
||||
{walletConnectionOption}
|
||||
{fortmaticOption}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function getModalContent() {
|
||||
@@ -334,7 +298,7 @@ export default function WalletModal({
|
||||
tryActivation={tryActivation}
|
||||
/>
|
||||
)}
|
||||
{walletView !== WALLET_VIEWS.PENDING && <OptionGrid data-cy="option-grid">{getOptions()}</OptionGrid>}
|
||||
{walletView !== WALLET_VIEWS.PENDING && <OptionGrid data-testid="option-grid">{getOptions()}</OptionGrid>}
|
||||
{!pendingError && (
|
||||
<LightCard>
|
||||
<AutoRow style={{ flexWrap: 'nowrap' }}>
|
||||
|
||||
@@ -1,38 +1,21 @@
|
||||
import { Web3ReactProvider } from '@web3-react/core'
|
||||
import { Web3ReactHooks, Web3ReactProvider } from '@web3-react/core'
|
||||
import { Connector } from '@web3-react/types'
|
||||
import { BACKFILLABLE_WALLETS, getConnectorForWallet, gnosisSafe, network, useConnectors } from 'connectors'
|
||||
import { ReactNode, useEffect } from 'react'
|
||||
import { useAppSelector } from 'state/hooks'
|
||||
|
||||
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}`)
|
||||
}
|
||||
}
|
||||
import { Connection } from 'connection'
|
||||
import { getConnectionName } from 'connection/utils'
|
||||
import useEagerlyConnect from 'hooks/useEagerlyConnect'
|
||||
import useOrderedConnections from 'hooks/useOrderedConnections'
|
||||
import { ReactNode, useMemo } from 'react'
|
||||
|
||||
export default function Web3Provider({ children }: { children: ReactNode }) {
|
||||
const selectedWalletBackfilled = useAppSelector((state) => state.user.selectedWalletBackfilled)
|
||||
const selectedWallet = useAppSelector((state) => state.user.selectedWallet)
|
||||
useEagerlyConnect()
|
||||
const connections = useOrderedConnections()
|
||||
const connectors: [Connector, Web3ReactHooks][] = connections.map(({ hooks, connector }) => [connector, hooks])
|
||||
|
||||
const connectors = useConnectors(selectedWallet)
|
||||
const key = useMemo(() => connections.map(({ type }: Connection) => getConnectionName(type)).join('-'), [connections])
|
||||
|
||||
useEffect(() => {
|
||||
connect(gnosisSafe)
|
||||
connect(network)
|
||||
|
||||
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>
|
||||
return (
|
||||
<Web3ReactProvider connectors={connectors} key={key}>
|
||||
{children}
|
||||
</Web3ReactProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
// 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 { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
|
||||
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
|
||||
import { getConnection } from 'connection/utils'
|
||||
import { getIsValidSwapQuote } from 'pages/Swap'
|
||||
import { darken } from 'polished'
|
||||
import { useMemo } from 'react'
|
||||
import { Activity } from 'react-feather'
|
||||
import { useAppSelector } from 'state/hooks'
|
||||
import { useDerivedSwapInfo } from 'state/swap/hooks'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
import { isChainAllowed } from 'utils/switchChain'
|
||||
|
||||
import { useHasSocks } from '../../hooks/useSocksBalance'
|
||||
import { useWalletModalToggle } from '../../state/application/hooks'
|
||||
import { useToggleWalletModal } from '../../state/application/hooks'
|
||||
import { isTransactionRecent, useAllTransactions } from '../../state/transactions/hooks'
|
||||
import { TransactionDetails } from '../../state/transactions/types'
|
||||
import { shortenAddress } from '../../utils'
|
||||
@@ -21,16 +24,6 @@ import Loader from '../Loader'
|
||||
import { RowBetween } from '../Row'
|
||||
import WalletModal from '../WalletModal'
|
||||
|
||||
const IconWrapper = styled.div<{ size?: number }>`
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
& > * {
|
||||
height: ${({ size }) => (size ? size + 'px' : '32px')};
|
||||
width: ${({ size }) => (size ? size + 'px' : '32px')};
|
||||
}
|
||||
`
|
||||
|
||||
const Web3StatusGeneric = styled(ButtonSecondary)`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
width: 100%;
|
||||
@@ -131,18 +124,16 @@ function Sock() {
|
||||
)
|
||||
}
|
||||
|
||||
function WrappedStatusIcon({ connector }: { connector: Connector }) {
|
||||
return (
|
||||
<IconWrapper size={16}>
|
||||
<StatusIcon connector={connector} />
|
||||
</IconWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
function Web3StatusInner() {
|
||||
const { account, connector, chainId, ENSName } = useWeb3React()
|
||||
const connectionType = getConnection(connector).type
|
||||
const {
|
||||
trade: { state: tradeState, trade },
|
||||
inputError: swapInputError,
|
||||
} = useDerivedSwapInfo()
|
||||
const validSwapQuote = getIsValidSwapQuote(trade, tradeState, swapInputError)
|
||||
|
||||
const error = useAppSelector((state) => state.wallet.errorByWallet[getWalletForConnector(connector)])
|
||||
const error = useAppSelector((state) => state.connection.errorByConnectionType[getConnection(connector).type])
|
||||
|
||||
const chainAllowed = chainId && isChainAllowed(connector, chainId)
|
||||
|
||||
@@ -157,7 +148,7 @@ function Web3StatusInner() {
|
||||
|
||||
const hasPendingTransactions = !!pending.length
|
||||
const hasSocks = useHasSocks()
|
||||
const toggleWalletModal = useWalletModalToggle()
|
||||
const toggleWalletModal = useToggleWalletModal()
|
||||
|
||||
if (!chainId) {
|
||||
return null
|
||||
@@ -181,7 +172,11 @@ function Web3StatusInner() {
|
||||
)
|
||||
} else if (account) {
|
||||
return (
|
||||
<Web3StatusConnected id="web3-status-connected" onClick={toggleWalletModal} pending={hasPendingTransactions}>
|
||||
<Web3StatusConnected
|
||||
data-testid="web3-status-connected"
|
||||
onClick={toggleWalletModal}
|
||||
pending={hasPendingTransactions}
|
||||
>
|
||||
{hasPendingTransactions ? (
|
||||
<RowBetween>
|
||||
<Text>
|
||||
@@ -195,16 +190,23 @@ function Web3StatusInner() {
|
||||
<Text>{ENSName || shortenAddress(account)}</Text>
|
||||
</>
|
||||
)}
|
||||
{!hasPendingTransactions && connector && <WrappedStatusIcon connector={connector} />}
|
||||
{!hasPendingTransactions && <StatusIcon connectionType={connectionType} />}
|
||||
</Web3StatusConnected>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Web3StatusConnect id="connect-wallet" onClick={toggleWalletModal} faded={!account}>
|
||||
<Text>
|
||||
<Trans>Connect Wallet</Trans>
|
||||
</Text>
|
||||
</Web3StatusConnect>
|
||||
<TraceEvent
|
||||
events={[Event.onClick]}
|
||||
name={EventName.CONNECT_WALLET_BUTTON_CLICKED}
|
||||
properties={{ received_swap_quote: validSwapQuote }}
|
||||
element={ElementName.CONNECT_WALLET_BUTTON}
|
||||
>
|
||||
<Web3StatusConnect onClick={toggleWalletModal} faded={!account}>
|
||||
<Text>
|
||||
<Trans>Connect Wallet</Trans>
|
||||
</Text>
|
||||
</Web3StatusConnect>
|
||||
</TraceEvent>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useEffect } from 'react'
|
||||
import { UaEventOptions } from 'react-ga4/types/ga4'
|
||||
import { RouteComponentProps } from 'react-router-dom'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { isMobile } from 'utils/userAgent'
|
||||
import { getCLS, getFCP, getFID, getLCP, Metric } from 'web-vitals'
|
||||
|
||||
@@ -63,7 +63,8 @@ function reportWebVitals({ name, delta, id }: Metric) {
|
||||
}
|
||||
|
||||
// tracks web vitals and pageviews
|
||||
export function useAnalyticsReporter({ pathname, search }: RouteComponentProps['location']) {
|
||||
export function useAnalyticsReporter() {
|
||||
const { pathname, search } = useLocation()
|
||||
useEffect(() => {
|
||||
getFCP(reportWebVitals)
|
||||
getFID(reportWebVitals)
|
||||
@@ -71,7 +72,7 @@ export function useAnalyticsReporter({ pathname, search }: RouteComponentProps['
|
||||
getCLS(reportWebVitals)
|
||||
}, [])
|
||||
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId } = useWeb3React()
|
||||
useEffect(() => {
|
||||
// cd1 - custom dimension 1 - chainId
|
||||
googleAnalytics.set({ cd1: chainId ?? 0 })
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { isAddress } from '@ethersproject/address'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useState } from 'react'
|
||||
import { Text } from 'rebass'
|
||||
import styled from 'styled-components/macro'
|
||||
@@ -46,7 +46,7 @@ const ConfirmedIcon = styled(ColumnCenter)`
|
||||
`
|
||||
|
||||
export default function AddressClaimModal({ isOpen, onDismiss }: { isOpen: boolean; onDismiss: () => void }) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId } = useWeb3React()
|
||||
|
||||
// state for smart contract input
|
||||
const [typed, setTyped] = useState('')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { isAddress } from '@ethersproject/address'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import JSBI from 'jsbi'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Text } from 'rebass'
|
||||
@@ -9,7 +9,7 @@ import styled from 'styled-components/macro'
|
||||
|
||||
import Circle from '../../assets/images/blue-loader.svg'
|
||||
import tokenLogo from '../../assets/images/token-logo.png'
|
||||
import { useModalOpen, useToggleSelfClaimModal } from '../../state/application/hooks'
|
||||
import { useModalIsOpen, useToggleSelfClaimModal } from '../../state/application/hooks'
|
||||
import { ApplicationModal } from '../../state/application/reducer'
|
||||
import { useClaimCallback, useUserClaimData, useUserUnclaimedAmount } from '../../state/claim/hooks'
|
||||
import { useUserHasSubmittedClaim } from '../../state/transactions/hooks'
|
||||
@@ -48,10 +48,10 @@ const SOCKS_AMOUNT = 1000
|
||||
const USER_AMOUNT = 400
|
||||
|
||||
export default function ClaimModal() {
|
||||
const isOpen = useModalOpen(ApplicationModal.SELF_CLAIM)
|
||||
const isOpen = useModalIsOpen(ApplicationModal.SELF_CLAIM)
|
||||
const toggleClaimModal = useToggleSelfClaimModal()
|
||||
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
const { account, chainId } = useWeb3React()
|
||||
|
||||
// used for UI loading states
|
||||
const [attempting, setAttempting] = useState<boolean>(false)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TransactionResponse } from '@ethersproject/providers'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import StakingRewardsJson from '@uniswap/liquidity-staker/build/StakingRewards.json'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ReactNode, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
@@ -34,7 +34,7 @@ interface StakingModalProps {
|
||||
}
|
||||
|
||||
export default function ClaimRewardModal({ isOpen, onDismiss, stakingInfo }: StakingModalProps) {
|
||||
const { account } = useActiveWeb3React()
|
||||
const { account } = useWeb3React()
|
||||
|
||||
// monitor call to help UI loading state
|
||||
const addTransaction = useTransactionAdder()
|
||||
|
||||
@@ -5,8 +5,8 @@ import styled from 'styled-components/macro'
|
||||
|
||||
import { BIG_INT_SECONDS_IN_WEEK } from '../../constants/misc'
|
||||
import { useColor } from '../../hooks/useColor'
|
||||
import useStablecoinPrice from '../../hooks/useStablecoinPrice'
|
||||
import { useTotalSupply } from '../../hooks/useTotalSupply'
|
||||
import useUSDCPrice from '../../hooks/useUSDCPrice'
|
||||
import { useV2Pair } from '../../hooks/useV2Pairs'
|
||||
import { StakingInfo } from '../../state/stake/hooks'
|
||||
import { StyledInternalLink, ThemedText } from '../../theme'
|
||||
@@ -104,7 +104,7 @@ export default function PoolCard({ stakingInfo }: { stakingInfo: StakingInfo })
|
||||
}
|
||||
|
||||
// get the USD value of staked WETH
|
||||
const USDPrice = useUSDCPrice(WETH)
|
||||
const USDPrice = useStablecoinPrice(WETH)
|
||||
const valueOfTotalStakedAmountInUSDC =
|
||||
valueOfTotalStakedAmountInWETH && USDPrice?.quote(valueOfTotalStakedAmountInWETH)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Trans } from '@lingui/macro'
|
||||
import StakingRewardsJson from '@uniswap/liquidity-staker/build/StakingRewards.json'
|
||||
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useV2LiquidityTokenPermit } from 'hooks/useV2LiquidityTokenPermit'
|
||||
import { useCallback, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
@@ -53,7 +53,7 @@ interface StakingModalProps {
|
||||
}
|
||||
|
||||
export default function StakingModal({ isOpen, onDismiss, stakingInfo, userLiquidityUnstaked }: StakingModalProps) {
|
||||
const { provider } = useActiveWeb3React()
|
||||
const { provider } = useWeb3React()
|
||||
|
||||
// track and parse user input
|
||||
const [typedValue, setTypedValue] = useState('')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TransactionResponse } from '@ethersproject/providers'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import StakingRewardsJson from '@uniswap/liquidity-staker/build/StakingRewards.json'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ReactNode, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
@@ -35,7 +35,7 @@ interface StakingModalProps {
|
||||
}
|
||||
|
||||
export default function UnstakingModal({ isOpen, onDismiss, stakingInfo }: StakingModalProps) {
|
||||
const { account } = useActiveWeb3React()
|
||||
const { account } = useWeb3React()
|
||||
|
||||
// monitor call to help UI loading state
|
||||
const addTransaction = useTransactionAdder()
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import Card from 'components/Card'
|
||||
import { LoadingRows } from 'components/Loader/styled'
|
||||
import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
import { useContext, useMemo } from 'react'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
@@ -24,7 +24,6 @@ interface AdvancedSwapDetailsProps {
|
||||
trade?: InterfaceTrade<Currency, Currency, TradeType>
|
||||
allowedSlippage: Percent
|
||||
syncing?: boolean
|
||||
hideRouteDiagram?: boolean
|
||||
hideInfoTooltips?: boolean
|
||||
}
|
||||
|
||||
@@ -53,7 +52,7 @@ export function AdvancedSwapDetails({
|
||||
hideInfoTooltips = false,
|
||||
}: AdvancedSwapDetailsProps) {
|
||||
const theme = useContext(ThemeContext)
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId } = useWeb3React()
|
||||
const nativeCurrency = useNativeCurrency()
|
||||
|
||||
const { expectedOutputAmount, priceImpact } = useMemo(() => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import AnimatedDropdown from 'components/AnimatedDropdown'
|
||||
import Card, { OutlineCard } from 'components/Card'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
@@ -7,7 +8,6 @@ import { LoadingOpacityContainer } from 'components/Loader/styled'
|
||||
import Row, { RowBetween, RowFixed } from 'components/Row'
|
||||
import { MouseoverTooltipContent } from 'components/Tooltip'
|
||||
import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { darken } from 'polished'
|
||||
import { useState } from 'react'
|
||||
import { ChevronDown, Info } from 'react-feather'
|
||||
@@ -126,7 +126,7 @@ export default function SwapDetailsDropdown({
|
||||
allowedSlippage,
|
||||
}: SwapDetailsInlineProps) {
|
||||
const theme = useTheme()
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId } = useWeb3React()
|
||||
const [showDetails, setShowDetails] = useState(false)
|
||||
|
||||
return (
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Text } from 'rebass'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
|
||||
import { useUSDCValue } from '../../hooks/useUSDCPrice'
|
||||
import { useStablecoinValue } from '../../hooks/useStablecoinPrice'
|
||||
import { ThemedText } from '../../theme'
|
||||
import { isAddress, shortenAddress } from '../../utils'
|
||||
import { computeFiatValuePriceImpact } from '../../utils/computeFiatValuePriceImpact'
|
||||
@@ -55,8 +55,8 @@ export default function SwapModalHeader({
|
||||
|
||||
const [showInverted, setShowInverted] = useState<boolean>(false)
|
||||
|
||||
const fiatValueInput = useUSDCValue(trade.inputAmount)
|
||||
const fiatValueOutput = useUSDCValue(trade.outputAmount)
|
||||
const fiatValueInput = useStablecoinValue(trade.inputAmount)
|
||||
const fiatValueOutput = useStablecoinValue(trade.outputAmount)
|
||||
|
||||
return (
|
||||
<AutoColumn gap={'4px'} style={{ marginTop: '1rem' }}>
|
||||
|
||||
@@ -3,13 +3,13 @@ import { Protocol } from '@uniswap/router-sdk'
|
||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import AnimatedDropdown from 'components/AnimatedDropdown'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { LoadingRows } from 'components/Loader/styled'
|
||||
import RoutingDiagram from 'components/RoutingDiagram/RoutingDiagram'
|
||||
import { AutoRow, RowBetween } from 'components/Row'
|
||||
import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import useAutoRouterSupported from 'hooks/useAutoRouterSupported'
|
||||
import { memo, useState } from 'react'
|
||||
import { Plus } from 'react-feather'
|
||||
@@ -50,7 +50,7 @@ export default memo(function SwapRoute({ trade, syncing, fixedOpen = false, ...r
|
||||
const autoRouterSupported = useAutoRouterSupported()
|
||||
const routes = getTokenPath(trade)
|
||||
const [open, setOpen] = useState(false)
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId } = useWeb3React()
|
||||
|
||||
const [darkMode] = useDarkModeManager()
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, Price } from '@uniswap/sdk-core'
|
||||
import useUSDCPrice from 'hooks/useUSDCPrice'
|
||||
import useStablecoinPrice from 'hooks/useStablecoinPrice'
|
||||
import { useCallback, useContext } from 'react'
|
||||
import { Text } from 'rebass'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
@@ -32,7 +32,7 @@ const StyledPriceContainer = styled.button`
|
||||
export default function TradePrice({ price, showInverted, setShowInverted }: TradePriceProps) {
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
const usdcPrice = useUSDCPrice(showInverted ? price.baseCurrency : price.quoteCurrency)
|
||||
const usdcPrice = useStablecoinPrice(showInverted ? price.baseCurrency : price.quoteCurrency)
|
||||
/*
|
||||
* calculate needed amount of decimal prices, for prices between 0.95-1.05 use 4 decimal places
|
||||
*/
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ButtonEmpty } from 'components/Button'
|
||||
import Card, { OutlineCard } from 'components/Card'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import Modal from 'components/Modal'
|
||||
import { AutoRow, RowBetween } from 'components/Row'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { CloseIcon, ExternalLink, ThemedText, Z_INDEX } from 'theme'
|
||||
@@ -52,7 +52,7 @@ export default function UnsupportedCurrencyFooter({
|
||||
show: boolean
|
||||
currencies: (Currency | undefined | null)[]
|
||||
}) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId } = useWeb3React()
|
||||
const [showDetails, setShowDetails] = useState(false)
|
||||
|
||||
const tokens =
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { isAddress } from '@ethersproject/address'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ReactNode, useState } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
@@ -8,8 +8,8 @@ import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
||||
|
||||
import { UNI } from '../../constants/tokens'
|
||||
import useENS from '../../hooks/useENS'
|
||||
import { useTokenBalance } from '../../state/connection/hooks'
|
||||
import { useDelegateCallback } from '../../state/governance/hooks'
|
||||
import { useTokenBalance } from '../../state/wallet/hooks'
|
||||
import { ThemedText } from '../../theme'
|
||||
import AddressInputPanel from '../AddressInputPanel'
|
||||
import { ButtonPrimary } from '../Button'
|
||||
@@ -42,7 +42,7 @@ interface VoteModalProps {
|
||||
}
|
||||
|
||||
export default function DelegateModal({ isOpen, onDismiss, title }: VoteModalProps) {
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
const { account, chainId } = useWeb3React()
|
||||
|
||||
// state for delegate input
|
||||
const [usingDelegate, setUsingDelegate] = useState(false)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useContext, useState } from 'react'
|
||||
import { ArrowUpCircle, X } from 'react-feather'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
@@ -41,7 +41,7 @@ interface ExecuteModalProps {
|
||||
}
|
||||
|
||||
export default function ExecuteModal({ isOpen, onDismiss, proposalId }: ExecuteModalProps) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId } = useWeb3React()
|
||||
const executeCallback = useExecuteCallback()
|
||||
|
||||
// monitor call to help UI loading state
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
|
||||
@@ -37,7 +37,7 @@ const EmptyState = ({ HeaderContent, SubHeaderContent }: EmptyStateProps) => (
|
||||
)
|
||||
|
||||
export default function ProposalEmptyState() {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId } = useWeb3React()
|
||||
if (chainId && chainId !== SupportedChainId.MAINNET) {
|
||||
return (
|
||||
<EmptyState
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useContext, useState } from 'react'
|
||||
import { ArrowUpCircle, X } from 'react-feather'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
@@ -41,7 +41,7 @@ interface QueueModalProps {
|
||||
}
|
||||
|
||||
export default function QueueModal({ isOpen, onDismiss, proposalId }: QueueModalProps) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId } = useWeb3React()
|
||||
const queueCallback = useQueueCallback()
|
||||
|
||||
// monitor call to help UI loading state
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useContext, useState } from 'react'
|
||||
import { ArrowUpCircle, X } from 'react-feather'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
@@ -44,7 +44,7 @@ interface VoteModalProps {
|
||||
}
|
||||
|
||||
export default function VoteModal({ isOpen, onDismiss, proposalId, voteOption }: VoteModalProps) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { chainId } = useWeb3React()
|
||||
const voteCallback = useVoteCallback()
|
||||
const { votes: availableVotes } = useUserVotes()
|
||||
|
||||
|
||||
100
src/connection/index.ts
Normal file
100
src/connection/index.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
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 Fortmatic from 'fortmatic'
|
||||
|
||||
import UNISWAP_LOGO_URL from '../assets/svg/logo.svg'
|
||||
import { RPC_URLS } from '../constants/networks'
|
||||
|
||||
export enum ConnectionType {
|
||||
INJECTED = 'INJECTED',
|
||||
COINBASE_WALLET = 'COINBASE_WALLET',
|
||||
WALLET_CONNECT = 'WALLET_CONNECT',
|
||||
FORTMATIC = 'FORTMATIC',
|
||||
NETWORK = 'NETWORK',
|
||||
GNOSIS_SAFE = 'GNOSIS_SAFE',
|
||||
}
|
||||
|
||||
export interface Connection {
|
||||
connector: Connector
|
||||
hooks: Web3ReactHooks
|
||||
type: ConnectionType
|
||||
}
|
||||
|
||||
function onError(error: Error) {
|
||||
console.debug(`web3-react error: ${error}`)
|
||||
}
|
||||
|
||||
const [web3Network, web3NetworkHooks] = initializeConnector<Network>(
|
||||
(actions) => new Network({ actions, urlMap: RPC_URLS, defaultChainId: 1 })
|
||||
)
|
||||
export const networkConnection: Connection = {
|
||||
connector: web3Network,
|
||||
hooks: web3NetworkHooks,
|
||||
type: ConnectionType.NETWORK,
|
||||
}
|
||||
|
||||
const [web3Injected, web3InjectedHooks] = initializeConnector<MetaMask>((actions) => new MetaMask({ actions, onError }))
|
||||
export const injectedConnection: Connection = {
|
||||
connector: web3Injected,
|
||||
hooks: web3InjectedHooks,
|
||||
type: ConnectionType.INJECTED,
|
||||
}
|
||||
|
||||
const [web3GnosisSafe, web3GnosisSafeHooks] = initializeConnector<GnosisSafe>((actions) => new GnosisSafe({ actions }))
|
||||
export const gnosisSafeConnection: Connection = {
|
||||
connector: web3GnosisSafe,
|
||||
hooks: web3GnosisSafeHooks,
|
||||
type: ConnectionType.GNOSIS_SAFE,
|
||||
}
|
||||
|
||||
const [web3WalletConnect, web3WalletConnectHooks] = initializeConnector<WalletConnect>(
|
||||
(actions) =>
|
||||
new WalletConnect({
|
||||
actions,
|
||||
options: {
|
||||
rpc: RPC_URLS,
|
||||
qrcode: true,
|
||||
},
|
||||
onError,
|
||||
})
|
||||
)
|
||||
export const walletConnectConnection: Connection = {
|
||||
connector: web3WalletConnect,
|
||||
hooks: web3WalletConnectHooks,
|
||||
type: ConnectionType.WALLET_CONNECT,
|
||||
}
|
||||
|
||||
const [web3Fortmatic, web3FortmaticHooks] = initializeConnector<EIP1193>(
|
||||
(actions) => new EIP1193({ actions, provider: new Fortmatic(process.env.REACT_APP_FORTMATIC_KEY).getProvider() })
|
||||
)
|
||||
export const fortmaticConnection: Connection = {
|
||||
connector: web3Fortmatic,
|
||||
hooks: web3FortmaticHooks,
|
||||
type: ConnectionType.FORTMATIC,
|
||||
}
|
||||
|
||||
const [web3CoinbaseWallet, web3CoinbaseWalletHooks] = initializeConnector<CoinbaseWallet>(
|
||||
(actions) =>
|
||||
new CoinbaseWallet({
|
||||
actions,
|
||||
options: {
|
||||
url: RPC_URLS[SupportedChainId.MAINNET],
|
||||
appName: 'Uniswap',
|
||||
appLogoUrl: UNISWAP_LOGO_URL,
|
||||
reloadOnDisconnect: false,
|
||||
},
|
||||
onError,
|
||||
})
|
||||
)
|
||||
export const coinbaseWalletConnection: Connection = {
|
||||
connector: web3CoinbaseWallet,
|
||||
hooks: web3CoinbaseWalletHooks,
|
||||
type: ConnectionType.COINBASE_WALLET,
|
||||
}
|
||||
72
src/connection/utils.ts
Normal file
72
src/connection/utils.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { Connector } from '@web3-react/types'
|
||||
import {
|
||||
coinbaseWalletConnection,
|
||||
ConnectionType,
|
||||
fortmaticConnection,
|
||||
gnosisSafeConnection,
|
||||
injectedConnection,
|
||||
networkConnection,
|
||||
walletConnectConnection,
|
||||
} from 'connection'
|
||||
|
||||
export function getIsInjected(): boolean {
|
||||
return Boolean(window.ethereum)
|
||||
}
|
||||
|
||||
export function getIsMetaMask(): boolean {
|
||||
return window.ethereum?.isMetaMask ?? false
|
||||
}
|
||||
|
||||
export function getIsCoinbaseWallet(): boolean {
|
||||
return window.ethereum?.isCoinbaseWallet ?? false
|
||||
}
|
||||
|
||||
const CONNECTIONS = [
|
||||
gnosisSafeConnection,
|
||||
injectedConnection,
|
||||
coinbaseWalletConnection,
|
||||
walletConnectConnection,
|
||||
fortmaticConnection,
|
||||
networkConnection,
|
||||
]
|
||||
export function getConnection(c: Connector | ConnectionType) {
|
||||
if (c instanceof Connector) {
|
||||
const connection = CONNECTIONS.find((connection) => connection.connector === c)
|
||||
if (!connection) {
|
||||
throw Error('unsupported connector')
|
||||
}
|
||||
return connection
|
||||
} else {
|
||||
switch (c) {
|
||||
case ConnectionType.INJECTED:
|
||||
return injectedConnection
|
||||
case ConnectionType.COINBASE_WALLET:
|
||||
return coinbaseWalletConnection
|
||||
case ConnectionType.WALLET_CONNECT:
|
||||
return walletConnectConnection
|
||||
case ConnectionType.FORTMATIC:
|
||||
return fortmaticConnection
|
||||
case ConnectionType.NETWORK:
|
||||
return networkConnection
|
||||
case ConnectionType.GNOSIS_SAFE:
|
||||
return gnosisSafeConnection
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getConnectionName(connectionType: ConnectionType, isMetaMask?: boolean) {
|
||||
switch (connectionType) {
|
||||
case ConnectionType.INJECTED:
|
||||
return isMetaMask ? 'MetaMask' : 'Injected'
|
||||
case ConnectionType.COINBASE_WALLET:
|
||||
return 'Coinbase Wallet'
|
||||
case ConnectionType.WALLET_CONNECT:
|
||||
return 'WalletConnect'
|
||||
case ConnectionType.FORTMATIC:
|
||||
return 'Fortmatic'
|
||||
case ConnectionType.NETWORK:
|
||||
return 'Network'
|
||||
case ConnectionType.GNOSIS_SAFE:
|
||||
return 'Gnosis Safe'
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
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 Fortmatic from 'fortmatic'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import UNISWAP_LOGO_URL from '../assets/svg/logo.svg'
|
||||
|
||||
export enum Wallet {
|
||||
INJECTED = 'INJECTED',
|
||||
COINBASE_WALLET = 'COINBASE_WALLET',
|
||||
WALLET_CONNECT = 'WALLET_CONNECT',
|
||||
FORTMATIC = 'FORTMATIC',
|
||||
NETWORK = 'NETWORK',
|
||||
GNOSIS_SAFE = 'GNOSIS_SAFE',
|
||||
}
|
||||
|
||||
export const BACKFILLABLE_WALLETS = [Wallet.COINBASE_WALLET, Wallet.WALLET_CONNECT, Wallet.INJECTED]
|
||||
export const SELECTABLE_WALLETS = [...BACKFILLABLE_WALLETS, Wallet.FORTMATIC]
|
||||
|
||||
function onError(error: Error) {
|
||||
console.debug(`web3-react error: ${error}`)
|
||||
}
|
||||
|
||||
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')
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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])
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user