Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8cdec6188c | ||
|
|
5325d0cbe5 | ||
|
|
c16e49e774 | ||
|
|
7e709e10db | ||
|
|
7389b178fd | ||
|
|
48f8c6a141 | ||
|
|
091876a374 | ||
|
|
d0e4aa832a | ||
|
|
b17a38d94b | ||
|
|
22136b2708 | ||
|
|
1897330ffc | ||
|
|
6131d0079f | ||
|
|
e6814994f6 | ||
|
|
fea7d3a867 | ||
|
|
f4f0f29409 | ||
|
|
fa25e3c3e5 | ||
|
|
51d2b3792f | ||
|
|
04ded04e74 | ||
|
|
cf188a86de | ||
|
|
f7e2435868 | ||
|
|
5817d3bbdb | ||
|
|
bdeb62ad64 | ||
|
|
e42effdbfd | ||
|
|
c0a5adf43d | ||
|
|
fd0489e654 | ||
|
|
caa0a2967d | ||
|
|
11ec2333dd | ||
|
|
251339a9ef | ||
|
|
148e415fe8 | ||
|
|
fd964c5b74 | ||
|
|
83b6eec271 | ||
|
|
cd76fffbbe | ||
|
|
2c0ac56296 | ||
|
|
f836e3ca32 | ||
|
|
1733fbb378 | ||
|
|
78142270a8 | ||
|
|
db4e2a9bee | ||
|
|
9cee94a473 | ||
|
|
db5a14387f | ||
|
|
5dc7d36669 | ||
|
|
a3cbe672c7 | ||
|
|
dc368ed7ac | ||
|
|
b109248b4c | ||
|
|
64cc6fb88c | ||
|
|
74f6a4ef3f | ||
|
|
f468001404 | ||
|
|
f26ec2ff1b | ||
|
|
61d1036d28 | ||
|
|
e11d2080a4 | ||
|
|
da33423719 | ||
|
|
bd4545538d | ||
|
|
4274db67d5 | ||
|
|
28498706cb | ||
|
|
68c71a67dd | ||
|
|
fe195b63f7 | ||
|
|
86b85e25a5 | ||
|
|
0ea635ce15 | ||
|
|
99ab581a87 | ||
|
|
fc571d0f63 | ||
|
|
2de43a8cdb | ||
|
|
5383436c88 | ||
|
|
521f3aae04 | ||
|
|
9318c1204b | ||
|
|
5055695b9b | ||
|
|
ae8c0377de | ||
|
|
8eaf1f4964 | ||
|
|
f717bf4a49 | ||
|
|
dcbd4e475d | ||
|
|
b704bdac94 | ||
|
|
00d3df95c0 | ||
|
|
251b8b703a | ||
|
|
ef8432437d |
24
.github/pull_request_template.md
vendored
Normal file
24
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
Your PR title must follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/#summary), and should start with one of the following [types](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#type):
|
||||
|
||||
- build: Changes that affect the build system or external dependencies (example scopes: yarn, eslint, typescript)
|
||||
- ci: Changes to our CI configuration files and scripts (example scopes: vercel, github, cypress)
|
||||
- docs: Documentation only changes
|
||||
- feat: A new feature
|
||||
- fix: A bug fix
|
||||
- perf: A code change that improves performance
|
||||
- refactor: A code change that neither fixes a bug nor adds a feature
|
||||
- style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
|
||||
- test: Adding missing tests or correcting existing tests
|
||||
|
||||
Example commit messages:
|
||||
|
||||
- feat: adds support for gnosis safe wallet
|
||||
- fix: removes a polling memory leak
|
||||
- chore: bumps redux version
|
||||
|
||||
Other things to note:
|
||||
|
||||
- Please describe the change using verb statements (ex: Removes X from Y)
|
||||
- PRs with multiple changes should use a list of verb statements
|
||||
- Add any relevant unit / integration tests
|
||||
- Changes will be previewable via vercel. Non-obvious changes should include instructions for how to reproduce them
|
||||
40
.github/workflows/bundle.yaml
vendored
40
.github/workflows/bundle.yaml
vendored
@@ -1,40 +0,0 @@
|
||||
name: Widgets
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- uses: actions/cache@v2
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: yarn widgets:build
|
||||
2
.github/workflows/crowdin-sync.yaml
vendored
2
.github/workflows/crowdin-sync.yaml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
run: "yarn i18n:extract"
|
||||
|
||||
- name: Synchronize
|
||||
uses: crowdin/github-action@1.1.0
|
||||
uses: crowdin/github-action@1.4.9
|
||||
with:
|
||||
upload_sources: false
|
||||
download_translations: true
|
||||
|
||||
9
.github/workflows/integration-tests.yaml
vendored
9
.github/workflows/integration-tests.yaml
vendored
@@ -37,14 +37,13 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- run: yarn cypress install
|
||||
|
||||
- run: yarn build
|
||||
env:
|
||||
CI: false
|
||||
REACT_APP_SERVICE_WORKER: false
|
||||
CI: false # disables lint checks when building
|
||||
|
||||
- run: yarn test:e2e
|
||||
- run: yarn serve &
|
||||
|
||||
- run: yarn cypress:run --record
|
||||
env:
|
||||
CYPRESS_INTEGRATION_TEST_PRIVATE_KEY: ${{ secrets.CYPRESS_INTEGRATION_TEST_PRIVATE_KEY }}
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -19,8 +19,6 @@
|
||||
|
||||
# builds
|
||||
/build
|
||||
/cosmos-export
|
||||
/dist
|
||||
/dts
|
||||
|
||||
# misc
|
||||
@@ -39,8 +37,6 @@ yarn-error.log*
|
||||
notes.txt
|
||||
.idea/
|
||||
|
||||
.vscode/
|
||||
|
||||
package-lock.json
|
||||
|
||||
cypress/videos
|
||||
|
||||
19
.vscode/settings.json
vendored
Normal file
19
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"npm.packageManager": "yarn",
|
||||
"typescript.updateImportsOnFileMove.enabled": "always",
|
||||
"javascript.updateImportsOnFileMove.enabled": "always",
|
||||
"editor.formatOnSaveMode": "file",
|
||||
"editor.tabCompletion": "on",
|
||||
"editor.tabSize": 2,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.inlineSuggest.enabled": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": true
|
||||
},
|
||||
"files.eol": "\n",
|
||||
"eslint.enable": true,
|
||||
"eslint.debug": true,
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,73 @@
|
||||
|
||||
# Contributing
|
||||
|
||||
Thank you for your interest in contributing to the Uniswap interface! 🦄
|
||||
|
||||
# Development
|
||||
|
||||
Before running anything, you'll need to install the dependencies:
|
||||
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
## Running the interface locally
|
||||
|
||||
1. `yarn install`
|
||||
1. `yarn start`
|
||||
```
|
||||
yarn start
|
||||
```
|
||||
|
||||
The interface should automatically open. If it does not, navigate to [http://localhost:3000].
|
||||
|
||||
## Creating a production build
|
||||
|
||||
1. `yarn install`
|
||||
1. `yarn build`
|
||||
```
|
||||
yarn build
|
||||
```
|
||||
|
||||
To serve the production build:
|
||||
|
||||
```
|
||||
yarn serve
|
||||
```
|
||||
|
||||
Then, navigate to [http://localhost:3000] to see it.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
```
|
||||
yarn test
|
||||
```
|
||||
|
||||
By default, this runs only unit tests that have been affected since the last commit. To run _all_ unit tests:
|
||||
|
||||
```
|
||||
yarn test --watchAll
|
||||
```
|
||||
|
||||
## Running integration tests (cypress)
|
||||
|
||||
Integration tests require a server to be running. In order to see your changes quickly, run `start` in its own tab/window:
|
||||
|
||||
```
|
||||
yarn start
|
||||
```
|
||||
|
||||
Integration tests are run using `cypress`. When developing locally, use `cypress:open` for an interactive UI, and to inspect the rendered page:
|
||||
|
||||
```
|
||||
yarn cypress:open
|
||||
```
|
||||
|
||||
To run _all_ cypress integration tests _from the command line_:
|
||||
|
||||
```
|
||||
yarn cypress:run
|
||||
```
|
||||
|
||||
## Engineering standards
|
||||
|
||||
Code merged into the `main` branch of this repository should adhere to high standards of correctness and maintainability.
|
||||
Use your best judgment when applying these standards. If code is in the critical path, will be frequently visited, or
|
||||
Code merged into the `main` branch of this repository should adhere to high standards of correctness and maintainability.
|
||||
Use your best judgment when applying these standards. If code is in the critical path, will be frequently visited, or
|
||||
makes large architectural changes, consider following all the standards.
|
||||
|
||||
- Have at least one engineer approve of large code refactorings
|
||||
@@ -39,7 +88,7 @@ The following points should help guide your development:
|
||||
- Avoid adding steps to the development/build processes
|
||||
- The build must be deterministic, i.e. a particular commit hash always produces the same build
|
||||
- Decentralization: anyone can run the interface
|
||||
- An Ethereum node should be the only critical dependency
|
||||
- An Ethereum node should be the only critical dependency
|
||||
- All other external dependencies should only enhance the UX ([graceful degradation](https://developer.mozilla.org/en-US/docs/Glossary/Graceful_degradation))
|
||||
- Accessibility: anyone can use the interface
|
||||
- The interface should be responsive, small and also run well on low performance devices (majority of swaps on mobile!)
|
||||
@@ -48,14 +97,14 @@ The following points should help guide your development:
|
||||
|
||||
Releases are cut automatically from the `main` branch Monday-Thursday in the morning according to the [release workflow](./.github/workflows/release.yaml).
|
||||
|
||||
Fix pull requests should be merged whenever ready and tested.
|
||||
Fix pull requests should be merged whenever ready and tested.
|
||||
If a fix is urgently needed in production, releases can be manually triggered on [GitHub](https://github.com/Uniswap/uniswap-interface/actions/workflows/release.yaml)
|
||||
after the fix is merged into `main`.
|
||||
|
||||
Features should not be merged into `main` until they are ready for users.
|
||||
When building larger features or collaborating with other developers, create a new branch from `main` to track its development.
|
||||
Use the automatic Vercel preview for sharing the feature to collect feedback.
|
||||
When the feature is ready for review, create a new pull request from the feature branch into `main` and request reviews from
|
||||
When the feature is ready for review, create a new pull request from the feature branch into `main` and request reviews from
|
||||
the appropriate UX reviewers (PMs or designers).
|
||||
|
||||
## Finding a first issue
|
||||
@@ -65,7 +114,7 @@ Start with issues with the label
|
||||
|
||||
# Translations
|
||||
|
||||
Uniswap uses [Crowdin](https://crowdin.com/project/uniswap-interface) for managing translations.
|
||||
Uniswap uses [Crowdin](https://crowdin.com/project/uniswap-interface) for managing translations.
|
||||
[This workflow](./.github/workflows/crowdin.yaml) uploads new strings for translation to the Crowdin project whenever code using the [lingui translation macros](https://lingui.js.org/ref/macro.html) is merged into `main`.
|
||||
|
||||
Every hour, translations are synced back down from Crowdin to the repository in [this other workflow](./.github/workflows/crowdin-sync.yaml).
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
# Uniswap Interface
|
||||
|
||||
An open source interface for Uniswap -- a protocol for decentralized exchange of Ethereum tokens.
|
||||
|
||||
- Website: [uniswap.org](https://uniswap.org/)
|
||||
- Interface: [app.uniswap.org](https://app.uniswap.org)
|
||||
- Docs: [uniswap.org/docs/](https://docs.uniswap.org/)
|
||||
- Twitter: [@Uniswap](https://twitter.com/Uniswap)
|
||||
- Reddit: [/r/Uniswap](https://www.reddit.com/r/Uniswap/)
|
||||
- Email: [contact@uniswap.org](mailto:contact@uniswap.org)
|
||||
- Discord: [Uniswap](https://discord.gg/FCfyBSbCU5)
|
||||
- Whitepapers:
|
||||
- [V1](https://hackmd.io/C-DvwDSfSxuh-Gd4WKE_ig)
|
||||
- [V2](https://uniswap.org/whitepaper.pdf)
|
||||
- [V3](https://uniswap.org/whitepaper-v3.pdf)
|
||||
|
||||
## Accessing the Uniswap Interface
|
||||
|
||||
To access the Uniswap Interface, use an IPFS gateway link from the
|
||||
[latest release](https://github.com/Uniswap/uniswap-interface/releases/latest),
|
||||
or visit [app.uniswap.org](https://app.uniswap.org).
|
||||
|
||||
## Unsupported tokens
|
||||
|
||||
Check out `useUnsupportedTokenList()` in [src/state/lists/hooks.ts](./src/state/lists/hooks.ts) for blocking tokens in your instance of the interface.
|
||||
|
||||
You can block an entire list of tokens by passing in a tokenlist like [here](./src/constants/lists.ts) or you can block specific tokens by adding them to [unsupported.tokenlist.json](./src/constants/tokenLists/unsupported.tokenlist.json).
|
||||
|
||||
## Contributions
|
||||
|
||||
For steps on local deployment, development, and code contribution, please see [CONTRIBUTING](./CONTRIBUTING.md).
|
||||
|
||||
## Accessing Uniswap V2
|
||||
|
||||
The Uniswap Interface supports swapping, adding liquidity, removing liquidity and migrating liquidity for Uniswap protocol V2.
|
||||
|
||||
- Swap on Uniswap V2: https://app.uniswap.org/#/swap?use=v2
|
||||
- View V2 liquidity: https://app.uniswap.org/#/pool/v2
|
||||
- Add V2 liquidity: https://app.uniswap.org/#/add/v2
|
||||
- Migrate V2 liquidity to V3: https://app.uniswap.org/#/migrate/v2
|
||||
|
||||
## 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).
|
||||
46
README.md
46
README.md
@@ -1,5 +1,3 @@
|
||||
This repo is home to the Uniswap Widgets package and the web app interface [app.uniswap.org](https://app.uniswap.org).
|
||||
|
||||
# Uniswap Labs Interface
|
||||
|
||||
[](https://github.com/Uniswap/interface/actions/workflows/unit-tests.yaml)
|
||||
@@ -8,14 +6,46 @@ This repo is home to the Uniswap Widgets package and the web app interface [app.
|
||||
[](https://github.com/Uniswap/interface/actions/workflows/release.yaml)
|
||||
[](https://crowdin.com/project/uniswap-interface)
|
||||
|
||||
The web application hosted at https://app.uniswap.org is a convenient way to access the core functionality of the Uniswap Protocol.
|
||||
An open source interface for Uniswap -- a protocol for decentralized exchange of Ethereum tokens.
|
||||
|
||||
For documentation of the interface including how to contribute or access prior builds, please view the README here: [INTERFACE_README.md](./INTERFACE_README.md)
|
||||
- Website: [uniswap.org](https://uniswap.org/)
|
||||
- Interface: [app.uniswap.org](https://app.uniswap.org)
|
||||
- Docs: [uniswap.org/docs/](https://docs.uniswap.org/)
|
||||
- Twitter: [@Uniswap](https://twitter.com/Uniswap)
|
||||
- Reddit: [/r/Uniswap](https://www.reddit.com/r/Uniswap/)
|
||||
- Email: [contact@uniswap.org](mailto:contact@uniswap.org)
|
||||
- Discord: [Uniswap](https://discord.gg/FCfyBSbCU5)
|
||||
- Whitepapers:
|
||||
- [V1](https://hackmd.io/C-DvwDSfSxuh-Gd4WKE_ig)
|
||||
- [V2](https://uniswap.org/whitepaper.pdf)
|
||||
- [V3](https://uniswap.org/whitepaper-v3.pdf)
|
||||
|
||||
# Uniswap Labs Widgets
|
||||
## Accessing the Uniswap Interface
|
||||
|
||||
The `@uniswap/widgets` package is an npm package of React components used to provide subsets of the Uniswap Protocol functionality in a small and configurable user interface element.
|
||||
To access the Uniswap Interface, use an IPFS gateway link from the
|
||||
[latest release](https://github.com/Uniswap/uniswap-interface/releases/latest),
|
||||
or visit [app.uniswap.org](https://app.uniswap.org).
|
||||
|
||||
The npm package can be found here. [@uniswap/widgets](https://www.npmjs.com/package/@uniswap/widgets)
|
||||
## Unsupported tokens
|
||||
|
||||
For documentation of the widgets package, please view the README here: [WIDGETS_README.md](./WIDGETS_README.md).
|
||||
Check out `useUnsupportedTokenList()` in [src/state/lists/hooks.ts](./src/state/lists/hooks.ts) for blocking tokens in your instance of the interface.
|
||||
|
||||
You can block an entire list of tokens by passing in a tokenlist like [here](./src/constants/lists.ts) or you can block specific tokens by adding them to [unsupported.tokenlist.json](./src/constants/tokenLists/unsupported.tokenlist.json).
|
||||
|
||||
## Contributions
|
||||
|
||||
For steps on local deployment, development, and code contribution, please see [CONTRIBUTING](./CONTRIBUTING.md).
|
||||
|
||||
## Accessing Uniswap V2
|
||||
|
||||
The Uniswap Interface supports swapping, adding liquidity, removing liquidity and migrating liquidity for Uniswap protocol V2.
|
||||
|
||||
- Swap on Uniswap V2: https://app.uniswap.org/#/swap?use=v2
|
||||
- View V2 liquidity: https://app.uniswap.org/#/pool/v2
|
||||
- Add V2 liquidity: https://app.uniswap.org/#/add/v2
|
||||
- Migrate V2 liquidity to V3: https://app.uniswap.org/#/migrate/v2
|
||||
|
||||
## 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).
|
||||
@@ -1,40 +0,0 @@
|
||||
# Uniswap Labs Swap Widget
|
||||
|
||||
The Swap Widget bundles the whole swapping experience into a single React component that developers can easily embed in their app with one line of code.
|
||||
|
||||

|
||||
|
||||
You can customize the theme (colors, fonts, border radius, and more) to match the style of your application. You can also configure your own default token list and optionally set a convenience fee on swaps executed through the widget on your site.
|
||||
|
||||
## Installation
|
||||
|
||||
Install the widgets library via `npm` or `yarn`. If you do not already use the widget's peerDependencies `redux` and `react-redux`, then you'll need to add them as well.
|
||||
|
||||
```js
|
||||
yarn add @uniswap/widgets redux react-redux
|
||||
```
|
||||
```js
|
||||
npm i --save @uniswap/widgets redux react-redux
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- [overview](https://docs.uniswap.org/sdk/widgets/swap-widget)
|
||||
- [api reference](https://docs.uniswap.org/sdk/widgets/swap-widget/api)
|
||||
|
||||
## Example Apps
|
||||
|
||||
Uniswap Labs maintains two demo apps in branches of the [widgets-demo](https://github.com/Uniswap/widgets-demo) repo:
|
||||
|
||||
- [NextJS](https://github.com/Uniswap/widgets-demo/tree/nextjs)
|
||||
- [Create React App](https://github.com/Uniswap/widgets-demo/tree/cra)
|
||||
|
||||
Others have also also released the widget in production to their userbase:
|
||||
|
||||
- [OpenSea](https://opensea.io/)
|
||||
- [Friends With Benefits](https://www.fwb.help/)
|
||||
- [Oasis](https://oasis.app/)
|
||||
|
||||
## Legal notice
|
||||
|
||||
Uniswap Labs encourages integrators to evaluate their own regulatory obligations when integrating this widget into their products, including, but not limited to, those related to economic or trade sanctions compliance.
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"watchDirs": [
|
||||
"src"
|
||||
],
|
||||
"webpack": {
|
||||
"configPath": "react-scripts/config/webpack.config",
|
||||
"overridePath": "cosmos.override.cjs"
|
||||
},
|
||||
"port": 5001
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const { DefinePlugin } = require('webpack')
|
||||
|
||||
// Renders the cosmos fixtures in isolation, instead of using public/index.html.
|
||||
module.exports = (webpackConfig) => ({
|
||||
...webpackConfig,
|
||||
plugins: webpackConfig.plugins.map((plugin) => {
|
||||
if (plugin instanceof HtmlWebpackPlugin) {
|
||||
return new HtmlWebpackPlugin({
|
||||
templateContent: '<body></body>',
|
||||
})
|
||||
}
|
||||
if (plugin instanceof DefinePlugin) {
|
||||
return new DefinePlugin({
|
||||
...plugin.definitions,
|
||||
'process.env': {
|
||||
...plugin.definitions['process.env'],
|
||||
REACT_APP_IS_WIDGET: true,
|
||||
REACT_APP_LOCALES: '"../locales"',
|
||||
},
|
||||
})
|
||||
}
|
||||
return plugin
|
||||
}),
|
||||
})
|
||||
@@ -1,16 +0,0 @@
|
||||
// Custom test environment to provide `TextEncoder`/`TextDecoder`
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const Environment = require('jest-environment-jsdom')
|
||||
|
||||
module.exports = class CustomTestEnvironment extends Environment {
|
||||
async setup() {
|
||||
await super.setup()
|
||||
if (typeof this.global.TextEncoder === 'undefined') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { TextEncoder, TextDecoder } = require('util')
|
||||
this.global.TextEncoder = TextEncoder
|
||||
this.global.TextDecoder = TextDecoder
|
||||
}
|
||||
}
|
||||
}
|
||||
20
cypress.config.ts
Normal file
20
cypress.config.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { defineConfig } from 'cypress'
|
||||
|
||||
export default defineConfig({
|
||||
projectId: 'yp82ef',
|
||||
video: false,
|
||||
defaultCommandTimeout: 10000,
|
||||
chromeWebSecurity: false,
|
||||
e2e: {
|
||||
setupNodeEvents(on, config) {
|
||||
return {
|
||||
...config,
|
||||
// Only enable Chrome.
|
||||
// Electron (the default) has issues injecting window.ethereum before pageload, so it is not viable.
|
||||
browsers: config.browsers.filter(({ name }) => name === 'chrome'),
|
||||
}
|
||||
},
|
||||
baseUrl: 'http://localhost:3000',
|
||||
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
|
||||
},
|
||||
})
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"projectId": "yp82ef",
|
||||
"baseUrl": "http://localhost:3000",
|
||||
"pluginsFile": false,
|
||||
"supportFile": "cypress/support/index.js",
|
||||
"video": false,
|
||||
"defaultCommandTimeout": 10000
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TEST_ADDRESS_NEVER_USE_SHORTENED } from '../support/commands'
|
||||
import { TEST_ADDRESS_NEVER_USE_SHORTENED } from '../support/ethereum'
|
||||
|
||||
describe('Landing Page', () => {
|
||||
beforeEach(() => cy.visit('/'))
|
||||
@@ -1,5 +1,6 @@
|
||||
describe('Pool', () => {
|
||||
beforeEach(() => cy.visit('/pool'))
|
||||
|
||||
it('add liquidity links to /add/ETH', () => {
|
||||
cy.get('#join-pool-button').click()
|
||||
cy.url().should('contain', '/add/ETH')
|
||||
84
cypress/e2e/service-worker.test.ts
Normal file
84
cypress/e2e/service-worker.test.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import assert = require('assert')
|
||||
|
||||
describe('Service Worker', () => {
|
||||
before(() => {
|
||||
// Fail fast if there is no Service Worker on this build.
|
||||
cy.request({ url: '/service-worker.js', headers: { 'Service-Worker': 'script' } }).then((response) => {
|
||||
const isValid = isValidServiceWorker(response)
|
||||
if (!isValid) {
|
||||
throw new Error(
|
||||
'\n' +
|
||||
'Service Worker tests must be run on a production-like build\n' +
|
||||
'To test, build with `yarn build:e2e` and serve with `yarn serve`'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
function isValidServiceWorker(response: Cypress.Response<any>) {
|
||||
const contentType = response.headers['content-type']
|
||||
return !(response.status === 404 || (contentType != null && contentType.indexOf('javascript') === -1))
|
||||
}
|
||||
})
|
||||
|
||||
function unregister() {
|
||||
return cy.log('unregister service worker').then(async () => {
|
||||
const cacheKeys = await window.caches.keys()
|
||||
const cacheKey = cacheKeys.find((key) => key.match(/precache/))
|
||||
if (cacheKey) {
|
||||
await window.caches.delete(cacheKey)
|
||||
}
|
||||
|
||||
const sw = await window.navigator.serviceWorker.getRegistration(Cypress.config().baseUrl ?? undefined)
|
||||
await sw?.unregister()
|
||||
})
|
||||
}
|
||||
before(unregister)
|
||||
after(unregister)
|
||||
|
||||
beforeEach(() => {
|
||||
cy.intercept({ hostname: 'www.google-analytics.com' }, (req) => {
|
||||
const body = req.body.toString()
|
||||
if (req.query['ep.event_category'] === 'Service Worker' || body.includes('Service%20Worker')) {
|
||||
if (req.query['en'] === 'Not Installed' || body.includes('Not%20Installed')) {
|
||||
req.alias = 'NotInstalled'
|
||||
} else if (req.query['en'] === 'Cache Hit' || body.includes('Cache%20Hit')) {
|
||||
req.alias = 'CacheHit'
|
||||
} else if (req.query['en'] === 'Cache Miss' || body.includes('Cache%20Miss')) {
|
||||
req.alias = 'CacheMiss'
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('installs a ServiceWorker', () => {
|
||||
cy.visit('/', { serviceWorker: true })
|
||||
.get('#swap-page')
|
||||
.wait('@NotInstalled', { timeout: 20000 })
|
||||
.window({ timeout: 20000 })
|
||||
.and((win) => {
|
||||
expect(win.navigator.serviceWorker.controller?.state).to.equal('activated')
|
||||
})
|
||||
})
|
||||
|
||||
it('records a cache hit', () => {
|
||||
cy.visit('/', { serviceWorker: true }).get('#swap-page').wait('@CacheHit', { timeout: 20000 })
|
||||
})
|
||||
|
||||
it('records a cache miss', () => {
|
||||
cy.then(async () => {
|
||||
const cacheKeys = await window.caches.keys()
|
||||
const cacheKey = cacheKeys.find((key) => key.match(/precache/))
|
||||
assert(cacheKey)
|
||||
|
||||
const cache = await window.caches.open(cacheKey)
|
||||
const keys = await cache.keys()
|
||||
const key = keys.find((key) => key.url.match(/index/))
|
||||
assert(key)
|
||||
|
||||
await cache.put(key, new Response())
|
||||
})
|
||||
.visit('/', { serviceWorker: true })
|
||||
.get('#swap-page')
|
||||
.wait('@CacheMiss', { timeout: 20000 })
|
||||
})
|
||||
})
|
||||
10
cypress/support/commands.d.ts
vendored
10
cypress/support/commands.d.ts
vendored
@@ -1,10 +0,0 @@
|
||||
export const TEST_ADDRESS_NEVER_USE: string
|
||||
|
||||
export const TEST_ADDRESS_NEVER_USE_SHORTENED: string
|
||||
|
||||
// declare namespace Cypress {
|
||||
// // eslint-disable-next-line @typescript-eslint/class-name-casing
|
||||
// interface cy {
|
||||
// additionalCommands(): void
|
||||
// }
|
||||
// }
|
||||
52
cypress/support/e2e.ts
Normal file
52
cypress/support/e2e.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
// ***********************************************************
|
||||
// This file is processed and loaded automatically before your test files.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.ts using ES2015 syntax:
|
||||
import { injected } from './ethereum'
|
||||
import assert = require('assert')
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Cypress {
|
||||
interface ApplicationWindow {
|
||||
ethereum: typeof injected
|
||||
}
|
||||
interface VisitOptions {
|
||||
serviceWorker?: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sets up the injected provider to be a mock ethereum provider with the given mnemonic/index
|
||||
// eslint-disable-next-line no-undef
|
||||
Cypress.Commands.overwrite(
|
||||
'visit',
|
||||
(original, url: string | Partial<Cypress.VisitOptions>, options?: Partial<Cypress.VisitOptions>) => {
|
||||
assert(typeof url === 'string')
|
||||
|
||||
cy.intercept('/service-worker.js', options?.serviceWorker ? undefined : { statusCode: 404 }).then(() => {
|
||||
original({
|
||||
...options,
|
||||
url: (url.startsWith('/') && url.length > 2 && !url.startsWith('/#') ? `/#${url}` : url) + '?chain=rinkeby',
|
||||
onBeforeLoad(win) {
|
||||
options?.onBeforeLoad?.(win)
|
||||
win.localStorage.clear()
|
||||
win.ethereum = injected
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
// Infura security policies are based on Origin headers.
|
||||
// These are stripped by cypress because chromeWebSecurity === false; this adds them back in.
|
||||
cy.intercept(/infura.io/, (res) => {
|
||||
res.headers['origin'] = 'http://localhost:3000'
|
||||
res.continue()
|
||||
})
|
||||
})
|
||||
@@ -1,8 +1,6 @@
|
||||
// ***********************************************
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
/**
|
||||
* Updates cy.visit() to include an injected window.ethereum provider.
|
||||
*/
|
||||
|
||||
import { Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge'
|
||||
import { JsonRpcProvider } from '@ethersproject/providers'
|
||||
@@ -20,14 +18,16 @@ export const TEST_ADDRESS_NEVER_USE_SHORTENED = `${TEST_ADDRESS_NEVER_USE.substr
|
||||
6
|
||||
)}...${TEST_ADDRESS_NEVER_USE.substr(-4, 4)}`
|
||||
|
||||
class CustomizedBridge extends Eip1193Bridge {
|
||||
const provider = new JsonRpcProvider('https://rinkeby.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847', 4)
|
||||
const signer = new Wallet(TEST_PRIVATE_KEY, provider)
|
||||
export const injected = new (class extends Eip1193Bridge {
|
||||
chainId = 4
|
||||
|
||||
async sendAsync(...args) {
|
||||
async sendAsync(...args: any[]) {
|
||||
console.debug('sendAsync called', ...args)
|
||||
return this.send(...args)
|
||||
}
|
||||
async send(...args) {
|
||||
async send(...args: any[]) {
|
||||
console.debug('send called', ...args)
|
||||
const isCallbackForm = typeof args[0] === 'object' && typeof args[1] === 'function'
|
||||
let callback
|
||||
@@ -71,19 +71,4 @@ class CustomizedBridge extends Eip1193Bridge {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sets up the injected provider to be a mock ethereum provider with the given mnemonic/index
|
||||
// eslint-disable-next-line no-undef
|
||||
Cypress.Commands.overwrite('visit', (original, url, options) => {
|
||||
return original(url.startsWith('/') && url.length > 2 && !url.startsWith('/#') ? `/#${url}` : url, {
|
||||
...options,
|
||||
onBeforeLoad(win) {
|
||||
options && options.onBeforeLoad && options.onBeforeLoad(win)
|
||||
win.localStorage.clear()
|
||||
const provider = new JsonRpcProvider('https://rinkeby.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847', 4)
|
||||
const signer = new Wallet(TEST_PRIVATE_KEY, provider)
|
||||
win.ethereum = new CustomizedBridge(signer, provider)
|
||||
},
|
||||
})
|
||||
})
|
||||
})(signer, provider)
|
||||
@@ -1,9 +0,0 @@
|
||||
// ***********************************************************
|
||||
// This file is processed and loaded automatically before your test files.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.ts using ES2015 syntax:
|
||||
import './commands'
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"baseUrl": "../node_modules",
|
||||
"target": "es5",
|
||||
"lib": ["es5", "dom"],
|
||||
"types": ["cypress"]
|
||||
|
||||
262
package.json
262
package.json
@@ -1,36 +1,62 @@
|
||||
{
|
||||
"name": "@uniswap/widgets",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.7",
|
||||
"description": "Uniswap Interface",
|
||||
"homepage": ".",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"type": "module",
|
||||
"types": "dist/index.d.ts",
|
||||
"main": "dist/cjs/index.cjs",
|
||||
"module": "dist/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/cjs/index.cjs"
|
||||
},
|
||||
"./locales/*": {
|
||||
"import": "./dist/locales/*.js",
|
||||
"require": "./dist/cjs/locales/*.cjs"
|
||||
},
|
||||
"./fonts.css": {
|
||||
"import": "./dist/fonts.css",
|
||||
"require": "./dist/fonts.css"
|
||||
"license": "GPL-3.0-or-later",
|
||||
"scripts": {
|
||||
"contracts:compile:abi": "typechain --target ethers-v5 --out-dir src/abis/types \"./src/abis/**/*.json\"",
|
||||
"contracts:compile:v3": "typechain --target ethers-v5 --out-dir src/types/v3 \"./node_modules/@uniswap/**/artifacts/contracts/**/*[!dbg].json\"",
|
||||
"contracts:compile": "yarn contracts:compile:abi && yarn contracts:compile:v3",
|
||||
"graphql:generate": "graphql-codegen --config codegen.yml",
|
||||
"prei18n:extract": "touch src/locales/en-US.po",
|
||||
"i18n:extract": "lingui extract --locale en-US",
|
||||
"i18n:compile": "yarn i18n:extract && lingui compile",
|
||||
"i18n:pseudo": "lingui extract --locale pseudo && lingui compile",
|
||||
"prepare": "yarn contracts:compile && yarn graphql:generate && yarn i18n:compile",
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"serve": "serve build -l 3000",
|
||||
"test": "react-scripts test --coverage",
|
||||
"cypress:open": "cypress open --browser chrome --e2e",
|
||||
"cypress:run": "cypress run --browser chrome --e2e"
|
||||
},
|
||||
"jest": {
|
||||
"collectCoverageFrom": [
|
||||
"src/components/**/*.ts*",
|
||||
"src/hooks/**/*.ts*",
|
||||
"src/lib/hooks/**/*.ts*",
|
||||
"src/lib/state/**/*.ts*",
|
||||
"src/lib/utils/**/*.ts*",
|
||||
"src/pages/**/*.ts*",
|
||||
"src/state/**/*.ts*",
|
||||
"src/utils/**/*.ts*"
|
||||
],
|
||||
"coverageThreshold": {
|
||||
"global": {
|
||||
"branches": 4,
|
||||
"functions": 6,
|
||||
"lines": 9,
|
||||
"statements": 9
|
||||
}
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-transform-runtime": "^7.17.0",
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
"@babel/preset-react": "^7.16.7",
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@ethersproject/experimental": "^5.4.0",
|
||||
"@fontsource/ibm-plex-mono": "^4.5.1",
|
||||
"@fontsource/inter": "^4.5.1",
|
||||
"@gnosis.pm/safe-apps-web3-react": "^0.6.0",
|
||||
"@graphql-codegen/cli": "1.21.5",
|
||||
"@graphql-codegen/typescript": "1.22.3",
|
||||
@@ -41,19 +67,11 @@
|
||||
"@lingui/macro": "^3.9.0",
|
||||
"@lingui/react": "^3.9.0",
|
||||
"@metamask/jazzicon": "^2.0.0",
|
||||
"@popperjs/core": "^2.4.4",
|
||||
"@reach/dialog": "^0.10.3",
|
||||
"@reach/portal": "^0.10.3",
|
||||
"@react-hook/window-scroll": "^1.3.0",
|
||||
"@rollup/plugin-alias": "^3.1.9",
|
||||
"@rollup/plugin-babel": "^5.3.0",
|
||||
"@rollup/plugin-commonjs": "^21.0.1",
|
||||
"@rollup/plugin-eslint": "^8.0.1",
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
"@rollup/plugin-node-resolve": "^13.1.3",
|
||||
"@rollup/plugin-replace": "^3.0.1",
|
||||
"@rollup/plugin-typescript": "^8.3.0",
|
||||
"@rollup/plugin-url": "^6.1.0",
|
||||
"@svgr/rollup": "^6.2.0",
|
||||
"@reduxjs/toolkit": "^1.6.1",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^12.0.0",
|
||||
"@testing-library/react-hooks": "^7.0.2",
|
||||
@@ -86,19 +104,32 @@
|
||||
"@uniswap/governance": "^1.0.2",
|
||||
"@uniswap/liquidity-staker": "^1.0.2",
|
||||
"@uniswap/merkle-distributor": "1.0.1",
|
||||
"@uniswap/redux-multicall": "^1.1.1",
|
||||
"@uniswap/router-sdk": "^1.0.3",
|
||||
"@uniswap/sdk-core": "^3.0.1",
|
||||
"@uniswap/smart-order-router": "^2.5.26",
|
||||
"@uniswap/token-lists": "^1.0.0-beta.27",
|
||||
"@uniswap/v2-core": "1.0.0",
|
||||
"@uniswap/v2-periphery": "^1.1.0-beta.0",
|
||||
"@uniswap/v2-sdk": "^3.0.1",
|
||||
"@uniswap/v3-core": "1.0.0",
|
||||
"@uniswap/v3-periphery": "^1.1.1",
|
||||
"@uniswap/v3-sdk": "^3.8.2",
|
||||
"@web3-react/core": "^8.0.23-beta.0",
|
||||
"@web3-react/eip1193": "^8.0.18-beta.0",
|
||||
"@web3-react/empty": "^8.0.12-beta.0",
|
||||
"@web3-react/metamask": "^8.0.19-beta.0",
|
||||
"@web3-react/types": "^8.0.12-beta.0",
|
||||
"@web3-react/url": "^8.0.17-beta.0",
|
||||
"@web3-react/walletconnect": "^8.0.26-beta.0",
|
||||
"ajv": "^6.12.3",
|
||||
"array.prototype.flat": "^1.2.4",
|
||||
"array.prototype.flatmap": "^1.2.4",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"cids": "^1.0.0",
|
||||
"copy-to-clipboard": "^3.2.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"cypress": "^7.7.0",
|
||||
"cypress": "^10.1.0",
|
||||
"d3": "^7.0.0",
|
||||
"env-cmd": "^10.1.0",
|
||||
"eslint": "^7.11.0",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-plugin-better-styled-components": "^1.1.2",
|
||||
@@ -107,112 +138,13 @@
|
||||
"eslint-plugin-react-hooks": "^4.0.0",
|
||||
"eslint-plugin-simple-import-sort": "^7.0.0",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"ethers": "^5.1.4",
|
||||
"firebase": "^9.1.3",
|
||||
"graphql": "^15.5.0",
|
||||
"graphql-request": "^3.4.0",
|
||||
"immer": "^9.0.6",
|
||||
"inter-ui": "^3.13.1",
|
||||
"jest-styled-components": "^7.0.5",
|
||||
"polyfill-object.fromentries": "^1.0.1",
|
||||
"prettier": "^2.2.1",
|
||||
"qs": "^6.9.4",
|
||||
"react": "^17.0.1",
|
||||
"react-confetti": "^6.0.0",
|
||||
"react-cosmos": "^5.6.6",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-ga4": "^1.4.1",
|
||||
"react-is": "^17.0.2",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-router-dom": "^5.0.0",
|
||||
"react-scripts": "^4.0.3",
|
||||
"react-spring": "^8.0.27",
|
||||
"react-use-gesture": "^6.0.14",
|
||||
"redux": "^4.1.2",
|
||||
"redux-localstorage-simple": "^2.3.1",
|
||||
"rollup": "^2.63.0",
|
||||
"rollup-plugin-copy": "^3.4.0",
|
||||
"rollup-plugin-delete": "^2.0.0",
|
||||
"rollup-plugin-dts": "^4.1.0",
|
||||
"rollup-plugin-multi-input": "^1.3.1",
|
||||
"rollup-plugin-node-externals": "^3.1.2",
|
||||
"rollup-plugin-scss": "^3.0.0",
|
||||
"rollup-plugin-typescript2": "^0.31.1",
|
||||
"sass": "^1.45.1",
|
||||
"serve": "^11.3.2",
|
||||
"start-server-and-test": "^1.11.0",
|
||||
"typechain": "^5.0.0",
|
||||
"typescript": "^4.4.3",
|
||||
"ua-parser-js": "^0.7.28",
|
||||
"use-count-up": "^2.2.5",
|
||||
"use-resize-observer": "^8.0.0",
|
||||
"wcag-contrast": "^3.0.0",
|
||||
"web-vitals": "^2.1.0",
|
||||
"web3-react-abstract-connector": "npm:@web3-react/abstract-connector@^6.0.7",
|
||||
"web3-react-fortmatic-connector": "npm:@web3-react/fortmatic-connector@^6.0.9",
|
||||
"web3-react-injected-connector": "npm:@web3-react/injected-connector@^6.0.7",
|
||||
"web3-react-types": "npm:@web3-react/types@^6.0.7",
|
||||
"web3-react-walletconnect-connector": "npm:@web3-react/walletconnect-connector@^7.0.2-alpha.0",
|
||||
"web3-react-walletlink-connector": "npm:@web3-react/walletlink-connector@^6.2.13",
|
||||
"workbox-core": "^6.1.0",
|
||||
"workbox-precaching": "^6.1.0",
|
||||
"workbox-routing": "^6.1.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"@walletconnect/ethereum-provider": "1.7.1"
|
||||
},
|
||||
"scripts": {
|
||||
"contracts:compile:abi": "typechain --target ethers-v5 --out-dir src/abis/types \"./src/abis/**/*.json\"",
|
||||
"contracts:compile:v3": "typechain --target ethers-v5 --out-dir src/types/v3 \"./node_modules/@uniswap/**/artifacts/contracts/**/*.json\"",
|
||||
"contracts:compile": "yarn contracts:compile:abi && yarn contracts:compile:v3",
|
||||
"graphql:generate": "graphql-codegen --config codegen.yml",
|
||||
"prei18n:extract": "touch src/locales/en-US.po",
|
||||
"i18n:extract": "lingui extract --locale en-US",
|
||||
"i18n:compile": "yarn i18n:extract && lingui compile",
|
||||
"i18n:pseudo": "lingui extract --locale pseudo && lingui compile",
|
||||
"prepare": "yarn contracts:compile && yarn graphql:generate && yarn i18n:compile",
|
||||
"prepublishOnly": "yarn widgets:build",
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test --env=./custom-test-env.cjs",
|
||||
"test:e2e": "start-server-and-test 'serve build -l 3000' http://localhost:3000 'cypress run --record'",
|
||||
"widgets:start": "cosmos",
|
||||
"widgets:build": "rollup --config --failAfterWarnings --configPlugin typescript2"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.17.0",
|
||||
"@fontsource/ibm-plex-mono": "^4.5.1",
|
||||
"@fontsource/inter": "^4.5.1",
|
||||
"@popperjs/core": "^2.4.4",
|
||||
"@reduxjs/toolkit": "^1.6.1",
|
||||
"@uniswap/redux-multicall": "^1.1.1",
|
||||
"@uniswap/router-sdk": "^1.0.3",
|
||||
"@uniswap/sdk-core": "^3.0.1",
|
||||
"@uniswap/smart-order-router": "^2.5.26",
|
||||
"@uniswap/token-lists": "^1.0.0-beta.27",
|
||||
"@uniswap/v2-sdk": "^3.0.1",
|
||||
"@uniswap/v3-sdk": "^3.8.2",
|
||||
"@web3-react/core": "^8.0.23-beta.0",
|
||||
"@web3-react/eip1193": "^8.0.18-beta.0",
|
||||
"@web3-react/empty": "^8.0.12-beta.0",
|
||||
"@web3-react/types": "^8.0.12-beta.0",
|
||||
"@web3-react/url": "^8.0.17-beta.0",
|
||||
"ajv": "^6.12.3",
|
||||
"cids": "^1.0.0",
|
||||
"ethers": "^5.1.4",
|
||||
"immer": "^9.0.6",
|
||||
"jotai": "^1.3.7",
|
||||
"jsbi": "^3.1.4",
|
||||
"make-plural": "^7.0.0",
|
||||
@@ -221,29 +153,55 @@
|
||||
"multihashes": "^4.0.2",
|
||||
"node-vibrant": "^3.2.1-alpha.1",
|
||||
"polished": "^3.3.2",
|
||||
"polyfill-object.fromentries": "^1.0.1",
|
||||
"popper-max-size-modifier": "^0.2.0",
|
||||
"prettier": "^2.2.1",
|
||||
"qs": "^6.9.4",
|
||||
"react": "^17.0.1",
|
||||
"react-confetti": "^6.0.0",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-feather": "^2.0.8",
|
||||
"react-ga4": "^1.4.1",
|
||||
"react-is": "^17.0.2",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-popper": "^2.2.3",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-router-dom": "^5.0.0",
|
||||
"react-scripts": "^4.0.3",
|
||||
"react-spring": "^8.0.27",
|
||||
"react-use-gesture": "^6.0.14",
|
||||
"react-virtualized-auto-sizer": "^1.0.2",
|
||||
"react-window": "^1.8.5",
|
||||
"rebass": "^4.0.7",
|
||||
"redux": "^4.1.2",
|
||||
"redux-localstorage-simple": "^2.3.1",
|
||||
"sass": "^1.45.1",
|
||||
"serve": "^11.3.2",
|
||||
"setimmediate": "^1.0.5",
|
||||
"start-server-and-test": "^1.11.0",
|
||||
"styled-components": "^5.3.0",
|
||||
"tiny-invariant": "^1.2.0",
|
||||
"typechain": "^5.0.0",
|
||||
"typescript": "^4.4.3",
|
||||
"ua-parser-js": "^0.7.28",
|
||||
"use-count-up": "^2.2.5",
|
||||
"use-resize-observer": "^8.0.0",
|
||||
"wcag-contrast": "^3.0.0",
|
||||
"web-vitals": "^2.1.0",
|
||||
"web3-react-abstract-connector": "npm:@web3-react/abstract-connector@^6.0.7",
|
||||
"web3-react-core": "npm:@web3-react/core@^6.0.9",
|
||||
"wicg-inert": "^3.1.1"
|
||||
"web3-react-fortmatic-connector": "npm:@web3-react/fortmatic-connector@^6.0.9",
|
||||
"web3-react-injected-connector": "npm:@web3-react/injected-connector@^6.0.7",
|
||||
"web3-react-types": "npm:@web3-react/types@^6.0.7",
|
||||
"web3-react-walletconnect-connector": "npm:@web3-react/walletconnect-connector@^7.0.2-alpha.0",
|
||||
"web3-react-walletlink-connector": "npm:@web3-react/walletlink-connector@^6.2.13",
|
||||
"wicg-inert": "^3.1.1",
|
||||
"workbox-core": "^6.1.0",
|
||||
"workbox-navigation-preload": "^6.1.0",
|
||||
"workbox-precaching": "^6.1.0",
|
||||
"workbox-routing": "^6.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": "^7.17.0",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-redux": "^7.2.2",
|
||||
"redux": "^4.1.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bufferutil": "^4.0.6",
|
||||
"encoding": "^0.1.13",
|
||||
"utf-8-validate": "^5.0.8"
|
||||
"resolutions": {
|
||||
"@walletconnect/ethereum-provider": "1.7.1"
|
||||
}
|
||||
}
|
||||
|
||||
182
rollup.config.ts
182
rollup.config.ts
@@ -1,182 +0,0 @@
|
||||
/**
|
||||
* Bundles the widgets library, which is released independently of the interface application.
|
||||
* This library lives in src/lib, but shares code with the interface application.
|
||||
*/
|
||||
|
||||
import alias from '@rollup/plugin-alias'
|
||||
import babel from '@rollup/plugin-babel'
|
||||
import commonjs from '@rollup/plugin-commonjs'
|
||||
import json from '@rollup/plugin-json'
|
||||
import resolve from '@rollup/plugin-node-resolve'
|
||||
import replace from '@rollup/plugin-replace'
|
||||
import typescript from '@rollup/plugin-typescript'
|
||||
import url from '@rollup/plugin-url'
|
||||
import svgr from '@svgr/rollup'
|
||||
import path from 'path'
|
||||
import { RollupWarning } from 'rollup'
|
||||
import copy from 'rollup-plugin-copy'
|
||||
import del from 'rollup-plugin-delete'
|
||||
import dts from 'rollup-plugin-dts'
|
||||
// @ts-ignore // missing types
|
||||
import multi from 'rollup-plugin-multi-input'
|
||||
import externals from 'rollup-plugin-node-externals'
|
||||
import sass from 'rollup-plugin-scss'
|
||||
import { CompilerOptions } from 'typescript'
|
||||
|
||||
const REPLACEMENTS = {
|
||||
'process.env.REACT_APP_IS_WIDGET': true,
|
||||
'process.env.REACT_APP_LOCALES': '"./locales"',
|
||||
// esm requires fully-specified paths:
|
||||
'react/jsx-runtime': 'react/jsx-runtime.js',
|
||||
}
|
||||
|
||||
const EXTENSIONS = ['.js', '.jsx', '.ts', '.tsx']
|
||||
const ASSET_EXTENSIONS = ['.png', '.svg']
|
||||
function isAsset(source: string) {
|
||||
const extname = path.extname(source)
|
||||
return extname && [...ASSET_EXTENSIONS, '.css', '.scss'].includes(extname)
|
||||
}
|
||||
|
||||
function isEthers(source: string) {
|
||||
// @ethersproject/* modules are provided by ethers, with the exception of experimental.
|
||||
return source.startsWith('@ethersproject/') && !source.endsWith('experimental')
|
||||
}
|
||||
|
||||
const TS_CONFIG = './tsconfig.lib.json'
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { baseUrl, paths }: CompilerOptions = require(TS_CONFIG).compilerOptions
|
||||
const aliases = Object.entries({ ...paths }).flatMap(([find, replacements]) => {
|
||||
return replacements.map((replacement) => ({
|
||||
find: path.dirname(find),
|
||||
replacement: path.join(__dirname, baseUrl || '.', path.dirname(replacement)),
|
||||
}))
|
||||
})
|
||||
|
||||
const plugins = [
|
||||
// Dependency resolution
|
||||
resolve({ extensions: EXTENSIONS }), // resolves third-party modules within node_modules/
|
||||
alias({ entries: aliases }), // resolves paths aliased through the tsconfig (babel does not use tsconfig path resolution)
|
||||
|
||||
// Source code transformation
|
||||
replace({ ...REPLACEMENTS, preventAssignment: true }),
|
||||
json(), // imports json as ES6; doing so enables type-checking and module resolution
|
||||
]
|
||||
|
||||
const check = {
|
||||
input: 'src/lib/index.tsx',
|
||||
output: { file: 'dist/widgets.tsc', inlineDynamicImports: true },
|
||||
external: (source: string) => isAsset(source) || isEthers(source),
|
||||
plugins: [
|
||||
externals({ exclude: ['constants'], deps: true, peerDeps: true }), // marks builtins, dependencies, and peerDependencies external
|
||||
...plugins,
|
||||
typescript({ tsconfig: TS_CONFIG }),
|
||||
],
|
||||
onwarn: squelchTranspilationWarnings, // this pipeline is only for typechecking and generating definitions
|
||||
}
|
||||
|
||||
const type = {
|
||||
input: 'dist/dts/lib/index.d.ts',
|
||||
output: { file: 'dist/index.d.ts' },
|
||||
external: (source: string) => isAsset(source) || isEthers(source),
|
||||
plugins: [
|
||||
externals({ exclude: ['constants'], deps: true, peerDeps: true }),
|
||||
dts({ compilerOptions: { baseUrl: 'dist/dts' } }),
|
||||
process.env.ROLLUP_WATCH ? undefined : del({ hook: 'buildEnd', targets: ['dist/widgets.tsc', 'dist/dts'] }),
|
||||
],
|
||||
}
|
||||
|
||||
/**
|
||||
* This exports scheme works for nextjs and for CRA5.
|
||||
*
|
||||
* It will also work for CRA4 if you use direct imports:
|
||||
* instead of `import { SwapWidget } from '@uniswap/widgets'`,
|
||||
* `import { SwapWidget } from '@uniswap/widgets/dist/index.js'`.
|
||||
* I do not know why CRA4 does not seem to use exports for resolution.
|
||||
*
|
||||
* Note that chunks are enabled. This is so the tokenlist spec can be loaded async,
|
||||
* to improve first load time (due to ajv). Locales are also in separate chunks.
|
||||
*
|
||||
* Lastly, note that JSON and lingui are bundled into the library, as neither are fully
|
||||
* supported/compatible with ES Modules. Both _could_ be bundled only with esm, but this
|
||||
* yields a less complex pipeline.
|
||||
*/
|
||||
|
||||
const transpile = {
|
||||
input: 'src/lib/index.tsx',
|
||||
output: [
|
||||
{
|
||||
dir: 'dist',
|
||||
format: 'esm',
|
||||
sourcemap: false,
|
||||
},
|
||||
{
|
||||
dir: 'dist/cjs',
|
||||
entryFileNames: '[name].cjs',
|
||||
chunkFileNames: '[name]-[hash].cjs',
|
||||
format: 'cjs',
|
||||
sourcemap: false,
|
||||
},
|
||||
],
|
||||
external: isEthers,
|
||||
plugins: [
|
||||
externals({
|
||||
exclude: [
|
||||
'constants',
|
||||
/@lingui\/(core|react)/, // @lingui incorrectly exports esm, so it must be bundled in
|
||||
/\.json$/, // esm does not support JSON loading, so it must be bundled in
|
||||
],
|
||||
deps: true,
|
||||
peerDeps: true,
|
||||
}),
|
||||
...plugins,
|
||||
|
||||
// Source code transformation
|
||||
url({ include: ASSET_EXTENSIONS.map((extname) => '**/*' + extname), limit: Infinity }), // imports assets as data URIs
|
||||
svgr({ exportType: 'named', svgo: false }), // imports svgs as React components
|
||||
sass({ output: 'dist/fonts.css' }), // generates fonts.css
|
||||
commonjs(), // transforms cjs dependencies into tree-shakeable ES modules
|
||||
|
||||
babel({
|
||||
babelHelpers: 'runtime',
|
||||
presets: ['@babel/preset-env', ['@babel/preset-react', { runtime: 'automatic' }], '@babel/preset-typescript'],
|
||||
extensions: EXTENSIONS,
|
||||
plugins: [
|
||||
'macros', // enables @lingui and styled-components macros
|
||||
'@babel/plugin-transform-runtime', // embeds the babel runtime for library distribution
|
||||
],
|
||||
}),
|
||||
],
|
||||
onwarn: squelchTypeWarnings, // this pipeline is only for transpilation
|
||||
}
|
||||
|
||||
const locales = {
|
||||
input: 'src/locales/*.js',
|
||||
output: [
|
||||
{
|
||||
dir: 'dist',
|
||||
format: 'esm',
|
||||
sourcemap: false,
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
copy({
|
||||
copyOnce: true,
|
||||
targets: [{ src: 'src/locales/*.js', dest: 'dist/cjs/locales', rename: (name) => `${name}.cjs` }],
|
||||
}),
|
||||
commonjs(),
|
||||
multi(),
|
||||
],
|
||||
}
|
||||
|
||||
const config = [check, type, transpile, locales]
|
||||
export default config
|
||||
|
||||
function squelchTranspilationWarnings(warning: RollupWarning, warn: (warning: RollupWarning) => void) {
|
||||
if (warning.pluginCode === 'TS5055') return
|
||||
warn(warning)
|
||||
}
|
||||
|
||||
function squelchTypeWarnings(warning: RollupWarning, warn: (warning: RollupWarning) => void) {
|
||||
if (warning.code === 'UNUSED_EXTERNAL_IMPORT') return
|
||||
warn(warning)
|
||||
}
|
||||
BIN
src/assets/images/tally.png
Normal file
BIN
src/assets/images/tally.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -1,49 +1,64 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import React from 'react'
|
||||
import useCopyClipboard from 'hooks/useCopyClipboard'
|
||||
import React, { useCallback } from 'react'
|
||||
import { CheckCircle, Copy } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import useCopyClipboard from '../../hooks/useCopyClipboard'
|
||||
import { LinkStyledButton } from '../../theme'
|
||||
import { LinkStyledButton } from 'theme'
|
||||
|
||||
const CopyIcon = styled(LinkStyledButton)`
|
||||
color: ${({ theme }) => theme.text3};
|
||||
color: ${({ color, theme }) => color || theme.text3};
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
text-decoration: none;
|
||||
font-size: 0.825rem;
|
||||
:hover,
|
||||
:active,
|
||||
:focus {
|
||||
text-decoration: none;
|
||||
color: ${({ theme }) => theme.text2};
|
||||
color: ${({ color, theme }) => color || theme.text2};
|
||||
}
|
||||
`
|
||||
const TransactionStatusText = styled.span`
|
||||
const StyledText = styled.span`
|
||||
margin-left: 0.25rem;
|
||||
font-size: 0.825rem;
|
||||
${({ theme }) => theme.flexRowNoWrap};
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
export default function CopyHelper(props: { toCopy: string; children?: React.ReactNode }) {
|
||||
const Copied = ({ iconSize }: { iconSize?: number }) => (
|
||||
<StyledText>
|
||||
<CheckCircle size={iconSize ?? '16'} />
|
||||
<StyledText>
|
||||
<Trans>Copied</Trans>
|
||||
</StyledText>
|
||||
</StyledText>
|
||||
)
|
||||
|
||||
const Icon = ({ iconSize }: { iconSize?: number }) => (
|
||||
<StyledText>
|
||||
<Copy size={iconSize ?? '16'} />
|
||||
</StyledText>
|
||||
)
|
||||
|
||||
interface BaseProps {
|
||||
toCopy: string
|
||||
color?: string
|
||||
iconSize?: number
|
||||
iconPosition?: 'left' | 'right'
|
||||
}
|
||||
export type CopyHelperProps = BaseProps & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, keyof BaseProps>
|
||||
|
||||
export default function CopyHelper({ color, toCopy, children, iconSize, iconPosition }: CopyHelperProps) {
|
||||
const [isCopied, setCopied] = useCopyClipboard()
|
||||
const copy = useCallback(() => {
|
||||
setCopied(toCopy)
|
||||
}, [toCopy, setCopied])
|
||||
|
||||
return (
|
||||
<CopyIcon onClick={() => setCopied(props.toCopy)}>
|
||||
{isCopied ? (
|
||||
<TransactionStatusText>
|
||||
<CheckCircle size={'16'} />
|
||||
<TransactionStatusText>
|
||||
<Trans>Copied</Trans>
|
||||
</TransactionStatusText>
|
||||
</TransactionStatusText>
|
||||
) : (
|
||||
<TransactionStatusText>
|
||||
<Copy size={'16'} />
|
||||
</TransactionStatusText>
|
||||
)}
|
||||
{isCopied ? '' : props.children}
|
||||
<CopyIcon onClick={copy} color={color}>
|
||||
{iconPosition === 'left' ? isCopied ? <Copied iconSize={iconSize} /> : <Icon iconSize={iconSize} /> : null}
|
||||
{iconPosition === 'left' && <> </>}
|
||||
{isCopied ? '' : children}
|
||||
{iconPosition === 'right' && <> </>}
|
||||
{iconPosition === 'right' ? isCopied ? <Copied iconSize={iconSize} /> : <Icon iconSize={iconSize} /> : null}
|
||||
</CopyIcon>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ import {
|
||||
DepositLiquidityStakingTransactionInfo,
|
||||
ExactInputSwapTransactionInfo,
|
||||
ExactOutputSwapTransactionInfo,
|
||||
ExecuteTransactionInfo,
|
||||
MigrateV2LiquidityToV3TransactionInfo,
|
||||
QueueTransactionInfo,
|
||||
RemoveLiquidityV3TransactionInfo,
|
||||
SubmitProposalTransactionInfo,
|
||||
TransactionInfo,
|
||||
@@ -25,7 +27,7 @@ import {
|
||||
VoteTransactionInfo,
|
||||
WithdrawLiquidityStakingTransactionInfo,
|
||||
WrapTransactionInfo,
|
||||
} from '../../state/transactions/actions'
|
||||
} from '../../state/transactions/types'
|
||||
|
||||
function formatAmount(amountRaw: string, decimals: number, sigFigs: number): string {
|
||||
return new Fraction(amountRaw, JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(decimals))).toSignificant(sigFigs)
|
||||
@@ -126,6 +128,16 @@ function VoteSummary({ info }: { info: VoteTransactionInfo }) {
|
||||
}
|
||||
}
|
||||
|
||||
function QueueSummary({ info }: { info: QueueTransactionInfo }) {
|
||||
const proposalKey = `${info.governorAddress}/${info.proposalId}`
|
||||
return <Trans>Queue proposal {proposalKey}.</Trans>
|
||||
}
|
||||
|
||||
function ExecuteSummary({ info }: { info: ExecuteTransactionInfo }) {
|
||||
const proposalKey = `${info.governorAddress}/${info.proposalId}`
|
||||
return <Trans>Execute proposal {proposalKey}.</Trans>
|
||||
}
|
||||
|
||||
function DelegateSummary({ info: { delegatee } }: { info: DelegateTransactionInfo }) {
|
||||
const { ENSName } = useENSName(delegatee)
|
||||
return <Trans>Delegate voting power to {ENSName ?? delegatee}</Trans>
|
||||
@@ -339,6 +351,12 @@ export function TransactionSummary({ info }: { info: TransactionInfo }) {
|
||||
case TransactionType.REMOVE_LIQUIDITY_V3:
|
||||
return <RemoveLiquidityV3Summary info={info} />
|
||||
|
||||
case TransactionType.QUEUE:
|
||||
return <QueueSummary info={info} />
|
||||
|
||||
case TransactionType.EXECUTE:
|
||||
return <ExecuteSummary info={info} />
|
||||
|
||||
case TransactionType.SUBMIT_PROPOSAL:
|
||||
return <SubmitProposalTransactionSummary info={info} />
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Connector } from '@web3-react/types'
|
||||
import CopyHelper from 'components/AccountDetails/Copy'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useCallback, useContext } from 'react'
|
||||
import { ExternalLink as LinkIcon } from 'react-feather'
|
||||
@@ -10,14 +11,13 @@ import { AbstractConnector } from 'web3-react-abstract-connector'
|
||||
import { ReactComponent as Close } from '../../assets/images/x.svg'
|
||||
import { injected, walletlink } from '../../connectors'
|
||||
import { SUPPORTED_WALLETS } from '../../constants/wallet'
|
||||
import { clearAllTransactions } from '../../state/transactions/actions'
|
||||
import { clearAllTransactions } from '../../state/transactions/reducer'
|
||||
import { ExternalLink, LinkStyledButton, ThemedText } from '../../theme'
|
||||
import { shortenAddress } from '../../utils'
|
||||
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
|
||||
import { ButtonSecondary } from '../Button'
|
||||
import StatusIcon from '../Identicon/StatusIcon'
|
||||
import { AutoRow } from '../Row'
|
||||
import Copy from './Copy'
|
||||
import Transaction from './Transaction'
|
||||
|
||||
const HeaderRow = styled.div`
|
||||
@@ -310,11 +310,11 @@ export default function AccountDetails({
|
||||
<AccountControl>
|
||||
<div>
|
||||
{account && (
|
||||
<Copy toCopy={account}>
|
||||
<CopyHelper toCopy={account} iconPosition="left">
|
||||
<span style={{ marginLeft: '4px' }}>
|
||||
<Trans>Copy Address</Trans>
|
||||
</span>
|
||||
</Copy>
|
||||
</CopyHelper>
|
||||
)}
|
||||
{chainId && account && (
|
||||
<AddressLink
|
||||
@@ -336,11 +336,11 @@ export default function AccountDetails({
|
||||
<AccountControl>
|
||||
<div>
|
||||
{account && (
|
||||
<Copy toCopy={account}>
|
||||
<CopyHelper toCopy={account} iconPosition="left">
|
||||
<span style={{ marginLeft: '4px' }}>
|
||||
<Trans>Copy Address</Trans>
|
||||
</span>
|
||||
</Copy>
|
||||
</CopyHelper>
|
||||
)}
|
||||
{chainId && account && (
|
||||
<AddressLink
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Trans } from '@lingui/macro'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { ReactNode, useMemo } from 'react'
|
||||
|
||||
// SDN OFAC addresses
|
||||
const BLOCKED_ADDRESSES: string[] = [
|
||||
'0x7Db418b5D567A4e0E8c59Ad71BE1FcE48f3E6107',
|
||||
'0x72a5843cc08275C8171E582972Aa4fDa8C397B2A',
|
||||
@@ -24,16 +23,18 @@ const BLOCKED_ADDRESSES: string[] = [
|
||||
'0x6acdfba02d390b97ac2b2d42a63e85293bcc1',
|
||||
'0x48549a34ae37b12f6a30566245176994e17c6',
|
||||
'0x5512d943ed1f7c8a43f3435c85f7ab68b30121',
|
||||
'0xc455f7fd3e0e12afd51fba5c106909934d8a0e',
|
||||
'0x3cbded43efdaf0fc77b9c55f6fc9988fcc9b757d',
|
||||
'0xC455f7fd3e0e12afd51fba5c106909934D8A0e4a',
|
||||
'0x3CBdeD43EFdAf0FC77b9C55F6fC9988fCC9b757d',
|
||||
'0x67d40EE1A85bf4a4Bb7Ffae16De985e8427B6b45',
|
||||
'0x6f1ca141a28907f78ebaa64fb83a9088b02a8352',
|
||||
'0x6acdfba02d390b97ac2b2d42a63e85293bcc160e',
|
||||
'0x6F1cA141A28907F78Ebaa64fb83A9088b02A8352',
|
||||
'0x6aCDFBA02D390b97Ac2b2d42A63E85293BCc160e',
|
||||
'0x48549a34ae37b12f6a30566245176994e17c6b4a',
|
||||
'0x5512d943ed1f7c8a43f3435c85f7ab68b30121b0',
|
||||
'0xc455f7fd3e0e12afd51fba5c106909934d8a0e4a',
|
||||
'0xC455f7fd3e0e12afd51fba5c106909934D8A0e4a',
|
||||
'0x629e7Da20197a5429d30da36E77d06CdF796b71A',
|
||||
'0x7FF9cFad3877F21d41Da833E2F775dB0569eE3D9',
|
||||
'0x098B716B8Aaf21512996dC57EB0615e2383E2f96',
|
||||
'0xfEC8A60023265364D066a1212fDE3930F6Ae8da7',
|
||||
]
|
||||
|
||||
export default function Blocklist({ children }: { children: ReactNode }) {
|
||||
|
||||
58
src/components/ConnectedAccountBlocked/index.tsx
Normal file
58
src/components/ConnectedAccountBlocked/index.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import CopyHelper from 'components/AccountDetails/Copy'
|
||||
import Column from 'components/Column'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import { AlertOctagon } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, ThemedText } from 'theme'
|
||||
|
||||
import Modal from '../Modal'
|
||||
|
||||
const ContentWrapper = styled(Column)`
|
||||
align-items: center;
|
||||
margin: 32px;
|
||||
text-align: center;
|
||||
`
|
||||
const WarningIcon = styled(AlertOctagon)`
|
||||
min-height: 22px;
|
||||
min-width: 22px;
|
||||
color: ${({ theme }) => theme.warning};
|
||||
`
|
||||
const Copy = styled(CopyHelper)`
|
||||
font-size: 12px;
|
||||
`
|
||||
|
||||
interface ConnectedAccountBlockedProps {
|
||||
account: string | null | undefined
|
||||
isOpen: boolean
|
||||
}
|
||||
|
||||
export default function ConnectedAccountBlocked(props: ConnectedAccountBlockedProps) {
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<Modal isOpen={props.isOpen} onDismiss={Function.prototype()}>
|
||||
<ContentWrapper>
|
||||
<WarningIcon />
|
||||
<ThemedText.LargeHeader lineHeight={2} marginBottom={1} marginTop={1}>
|
||||
<Trans>Blocked Address</Trans>
|
||||
</ThemedText.LargeHeader>
|
||||
<ThemedText.DarkGray fontSize={12} marginBottom={12}>
|
||||
{props.account}
|
||||
</ThemedText.DarkGray>
|
||||
<ThemedText.Main fontSize={14} marginBottom={12}>
|
||||
<Trans>This address is blocked on the Uniswap Labs interface because it is associated with one or more</Trans>{' '}
|
||||
<ExternalLink href="https://help.uniswap.org/en/articles/6149816">
|
||||
<Trans>blocked activities</Trans>
|
||||
</ExternalLink>
|
||||
.
|
||||
</ThemedText.Main>
|
||||
<ThemedText.Main fontSize={12}>
|
||||
<Trans>If you believe this is an error, please send an email including your address to </Trans>{' '}
|
||||
</ThemedText.Main>
|
||||
<Copy iconSize={12} toCopy="compliance@uniswap.org" color={theme.primary1} iconPosition="right">
|
||||
compliance@uniswap.org
|
||||
</Copy>
|
||||
</ContentWrapper>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -27,13 +27,16 @@ export function FiatValue({
|
||||
return theme.red1
|
||||
}, [priceImpact, theme.green1, theme.red1, theme.text3, theme.yellow1])
|
||||
|
||||
const p = Number(fiatValue?.toFixed())
|
||||
const visibleDecimalPlaces = p < 1.05 ? 4 : 2
|
||||
|
||||
return (
|
||||
<ThemedText.Body fontSize={14} color={fiatValue ? theme.text3 : theme.text4}>
|
||||
{fiatValue ? (
|
||||
<Trans>
|
||||
$
|
||||
<HoverInlineText
|
||||
text={fiatValue?.toSignificant(6, { groupSeparator: ',' })}
|
||||
text={fiatValue?.toFixed(visibleDecimalPlaces, { groupSeparator: ',' })}
|
||||
textColor={fiatValue ? theme.text3 : theme.text4}
|
||||
/>
|
||||
</Trans>
|
||||
|
||||
@@ -5,17 +5,23 @@ import styled from 'styled-components/macro'
|
||||
|
||||
import Logo from '../Logo'
|
||||
|
||||
const StyledLogo = styled(Logo)<{ size: string; native: boolean }>`
|
||||
const StyledLogo = styled(Logo)<{ size: string }>`
|
||||
width: ${({ size }) => size};
|
||||
height: ${({ size }) => size};
|
||||
background: radial-gradient(white 50%, #ffffff00 calc(75% + 1px), #ffffff00 100%);
|
||||
border-radius: 50%;
|
||||
-mox-box-shadow: 0 0 1px ${({ native }) => (native ? 'white' : 'black')};
|
||||
-webkit-box-shadow: 0 0 1px ${({ native }) => (native ? 'white' : 'black')};
|
||||
box-shadow: 0 0 1px ${({ native }) => (native ? 'white' : 'black')};
|
||||
-mox-box-shadow: 0 0 1px black;
|
||||
-webkit-box-shadow: 0 0 1px black;
|
||||
box-shadow: 0 0 1px black;
|
||||
border: 0px solid rgba(255, 255, 255, 0);
|
||||
`
|
||||
|
||||
const StyledNativeLogo = styled(StyledLogo)`
|
||||
-mox-box-shadow: 0 0 1px white;
|
||||
-webkit-box-shadow: 0 0 1px white;
|
||||
box-shadow: 0 0 1px white;
|
||||
`
|
||||
|
||||
export default function CurrencyLogo({
|
||||
currency,
|
||||
size = '24px',
|
||||
@@ -26,16 +32,13 @@ export default function CurrencyLogo({
|
||||
size?: string
|
||||
style?: React.CSSProperties
|
||||
}) {
|
||||
const logoURIs = useCurrencyLogoURIs(currency)
|
||||
const props = {
|
||||
alt: `${currency?.symbol ?? 'token'} logo`,
|
||||
size,
|
||||
srcs: useCurrencyLogoURIs(currency),
|
||||
style,
|
||||
...rest,
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledLogo
|
||||
size={size}
|
||||
native={currency?.isNative ?? false}
|
||||
srcs={logoURIs}
|
||||
alt={`${currency?.symbol ?? 'token'} logo`}
|
||||
style={style}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
return currency?.isNative ? <StyledNativeLogo {...props} /> : <StyledLogo {...props} />
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import React, { ErrorInfo } from 'react'
|
||||
import ReactGA from 'react-ga4'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import store, { AppState } from '../../state'
|
||||
@@ -85,7 +85,10 @@ export default class ErrorBoundary extends React.Component<unknown, ErrorBoundar
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
ReactGA.event('exception', { description: error.toString() + errorInfo.toString(), fatal: true })
|
||||
sendEvent('exception', {
|
||||
description: error.toString() + errorInfo.toString(),
|
||||
fatal: true,
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { ButtonGray } from 'components/Button'
|
||||
import Card from 'components/Card'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
@@ -10,8 +11,7 @@ import { useFeeTierDistribution } from 'hooks/useFeeTierDistribution'
|
||||
import { PoolState, usePools } from 'hooks/usePools'
|
||||
import usePrevious from 'hooks/usePrevious'
|
||||
import { DynamicSection } from 'pages/AddLiquidity/styled'
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import ReactGA from 'react-ga4'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Box } from 'rebass'
|
||||
import styled, { keyframes } from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
@@ -101,7 +101,7 @@ export default function FeeSelector({
|
||||
|
||||
const handleFeePoolSelectWithEvent = useCallback(
|
||||
(fee: FeeAmount) => {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'FeePoolSelect',
|
||||
action: 'Manual',
|
||||
})
|
||||
@@ -122,7 +122,7 @@ export default function FeeSelector({
|
||||
setShowOptions(false)
|
||||
|
||||
recommended.current = true
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'FeePoolSelect',
|
||||
action: ' Recommended',
|
||||
})
|
||||
|
||||
@@ -93,6 +93,13 @@ const FlyoutRowActiveIndicator = styled.div`
|
||||
height: 9px;
|
||||
width: 9px;
|
||||
`
|
||||
|
||||
const CircleContainer = styled.div`
|
||||
width: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
const LinkOutCircle = styled(ArrowDownCircle)`
|
||||
transform: rotate(230deg);
|
||||
width: 16px;
|
||||
@@ -146,7 +153,7 @@ const BridgeLabel = ({ chainId }: { chainId: SupportedChainId }) => {
|
||||
return <Trans>Arbitrum Bridge</Trans>
|
||||
case SupportedChainId.OPTIMISM:
|
||||
case SupportedChainId.OPTIMISTIC_KOVAN:
|
||||
return <Trans>Optimism Gateway</Trans>
|
||||
return <Trans>Optimism Bridge</Trans>
|
||||
case SupportedChainId.POLYGON:
|
||||
case SupportedChainId.POLYGON_MUMBAI:
|
||||
return <Trans>Polygon Bridge</Trans>
|
||||
@@ -188,7 +195,11 @@ function Row({
|
||||
<FlyoutRow onClick={() => onSelectChain(targetChain)} active={active}>
|
||||
<Logo src={logoUrl} />
|
||||
<NetworkLabel>{label}</NetworkLabel>
|
||||
{chainId === targetChain && <FlyoutRowActiveIndicator />}
|
||||
{chainId === targetChain && (
|
||||
<CircleContainer>
|
||||
<FlyoutRowActiveIndicator />
|
||||
</CircleContainer>
|
||||
)}
|
||||
</FlyoutRow>
|
||||
)
|
||||
|
||||
@@ -197,21 +208,30 @@ function Row({
|
||||
<ActiveRowWrapper>
|
||||
{rowContent}
|
||||
<ActiveRowLinkList>
|
||||
{bridge ? (
|
||||
{bridge && (
|
||||
<ExternalLink href={bridge}>
|
||||
<BridgeLabel chainId={chainId} /> <LinkOutCircle />
|
||||
<BridgeLabel chainId={chainId} />
|
||||
<CircleContainer>
|
||||
<LinkOutCircle />
|
||||
</CircleContainer>
|
||||
</ExternalLink>
|
||||
) : null}
|
||||
{explorer ? (
|
||||
)}
|
||||
{explorer && (
|
||||
<ExternalLink href={explorer}>
|
||||
<ExplorerLabel chainId={chainId} /> <LinkOutCircle />
|
||||
<ExplorerLabel chainId={chainId} />
|
||||
<CircleContainer>
|
||||
<LinkOutCircle />
|
||||
</CircleContainer>
|
||||
</ExternalLink>
|
||||
) : null}
|
||||
{helpCenterUrl ? (
|
||||
)}
|
||||
{helpCenterUrl && (
|
||||
<ExternalLink href={helpCenterUrl}>
|
||||
<Trans>Help Center</Trans> <LinkOutCircle />
|
||||
<Trans>Help Center</Trans>
|
||||
<CircleContainer>
|
||||
<LinkOutCircle />
|
||||
</CircleContainer>
|
||||
</ExternalLink>
|
||||
) : null}
|
||||
)}
|
||||
</ActiveRowLinkList>
|
||||
</ActiveRowWrapper>
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, Price, Token } from '@uniswap/sdk-core'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { AutoColumn, ColumnCenter } from 'components/Column'
|
||||
import Loader from 'components/Loader'
|
||||
import { format } from 'd3'
|
||||
@@ -9,7 +10,6 @@ import useTheme from 'hooks/useTheme'
|
||||
import { saturate } from 'polished'
|
||||
import React, { ReactNode, useCallback, useMemo } from 'react'
|
||||
import { BarChart2, CloudOff, Inbox } from 'react-feather'
|
||||
import ReactGA from 'react-ga4'
|
||||
import { batch } from 'react-redux'
|
||||
import { Bound } from 'state/mint/v3/actions'
|
||||
import styled from 'styled-components/macro'
|
||||
@@ -158,7 +158,7 @@ export default function LiquidityChartRangeInput({
|
||||
)
|
||||
|
||||
if (isError) {
|
||||
ReactGA.event('exception', { description: error.toString(), fatal: false })
|
||||
sendEvent('exception', { description: error.toString(), fatal: false })
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { Heart, X } from 'react-feather'
|
||||
import ReactGA from 'react-ga4'
|
||||
import styled, { keyframes } from 'styled-components/macro'
|
||||
|
||||
import tokenLogo from '../../assets/images/token-logo.png'
|
||||
@@ -65,7 +65,7 @@ export default function ClaimPopup() {
|
||||
const showClaimModal = useModalOpen(ApplicationModal.SELF_CLAIM)
|
||||
const toggleSelfClaimModal = useToggleSelfClaimModal()
|
||||
const handleToggleSelfClaimModal = useCallback(() => {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'MerkleDrop',
|
||||
action: 'Toggle self claim modal',
|
||||
})
|
||||
@@ -79,7 +79,7 @@ export default function ClaimPopup() {
|
||||
// listen for available claim and show popup if needed
|
||||
useEffect(() => {
|
||||
if (userHasAvailableclaim) {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'MerkleDrop',
|
||||
action: 'Show claim popup',
|
||||
})
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { RowFixed } from 'components/Row'
|
||||
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
|
||||
import { useEffect } from 'react'
|
||||
import { MessageCircle, X } from 'react-feather'
|
||||
import ReactGA from 'react-ga4'
|
||||
import { useShowSurveyPopup } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, ThemedText, Z_INDEX } from 'theme'
|
||||
@@ -62,7 +62,7 @@ export default function SurveyPopup() {
|
||||
if (Math.random() < 0.01) {
|
||||
setShowSurveyPopup(true)
|
||||
// log a case of succesful view
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Survey',
|
||||
action: 'Saw Survey',
|
||||
})
|
||||
@@ -80,7 +80,7 @@ export default function SurveyPopup() {
|
||||
<Wrapper gap="10px">
|
||||
<WrappedCloseIcon
|
||||
onClick={() => {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Survey',
|
||||
action: 'Clicked Survey Link',
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { ButtonText } from 'components/Button'
|
||||
import PositionListItem from 'components/PositionListItem'
|
||||
import Toggle from 'components/Toggle'
|
||||
import React from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { MEDIA_WIDTHS } from 'theme'
|
||||
@@ -28,9 +28,37 @@ const MobileHeader = styled.div`
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: ${MEDIA_WIDTHS.upToExtraSmall}px) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
}
|
||||
`
|
||||
|
||||
const ToggleWrap = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const ToggleLabel = styled.div`
|
||||
opacity: 0.6;
|
||||
margin-right: 10px;
|
||||
`
|
||||
|
||||
const MobileTogglePosition = styled.div`
|
||||
@media screen and (max-width: ${MEDIA_WIDTHS.upToExtraSmall}px) {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
}
|
||||
`
|
||||
|
||||
type PositionListProps = React.PropsWithChildren<{
|
||||
@@ -51,12 +79,35 @@ export default function PositionList({
|
||||
<Trans>Your positions</Trans>
|
||||
{positions && ' (' + positions.length + ')'}
|
||||
</div>
|
||||
<ButtonText style={{ opacity: 0.6 }} onClick={() => setUserHideClosedPositions(!userHideClosedPositions)}>
|
||||
<Trans>Hide closed positions</Trans>
|
||||
</ButtonText>
|
||||
<ToggleWrap>
|
||||
<ToggleLabel>
|
||||
<Trans>Show closed positions</Trans>
|
||||
</ToggleLabel>
|
||||
<Toggle
|
||||
id="desktop-hide-closed-positions"
|
||||
isActive={!userHideClosedPositions}
|
||||
toggle={() => {
|
||||
setUserHideClosedPositions(!userHideClosedPositions)
|
||||
}}
|
||||
/>
|
||||
</ToggleWrap>
|
||||
</DesktopHeader>
|
||||
<MobileHeader>
|
||||
<Trans>Your positions</Trans>
|
||||
<ToggleWrap>
|
||||
<ToggleLabel>
|
||||
<Trans>Show closed positions</Trans>
|
||||
</ToggleLabel>
|
||||
<MobileTogglePosition>
|
||||
<Toggle
|
||||
id="mobile-hide-closed-positions"
|
||||
isActive={!userHideClosedPositions}
|
||||
toggle={() => {
|
||||
setUserHideClosedPositions(!userHideClosedPositions)
|
||||
}}
|
||||
/>
|
||||
</MobileTogglePosition>
|
||||
</ToggleWrap>
|
||||
</MobileHeader>
|
||||
{positions.map((p) => {
|
||||
return <PositionListItem key={p.tokenId.toString()} positionDetails={p} />
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import Card, { DarkGreyCard } from 'components/Card'
|
||||
import Row, { AutoRow, RowBetween } from 'components/Row'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { ArrowDown, Info, X } from 'react-feather'
|
||||
import ReactGA from 'react-ga4'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, ThemedText } from 'theme'
|
||||
import { isMobile } from 'utils/userAgent'
|
||||
@@ -87,7 +87,7 @@ export function PrivacyPolicyModal() {
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Modal',
|
||||
action: 'Show Legal',
|
||||
})
|
||||
@@ -167,11 +167,13 @@ export function PrivacyPolicy() {
|
||||
</AutoColumn>
|
||||
</DarkGreyCard>
|
||||
))}
|
||||
<Row justify="center" marginBottom="1rem">
|
||||
<ExternalLink href="https://help.uniswap.org/en/articles/5675203-terms-of-service-faq">
|
||||
<Trans>Learn more</Trans>
|
||||
</ExternalLink>
|
||||
</Row>
|
||||
<ThemedText.Body fontSize={12}>
|
||||
<Row justify="center" marginBottom="1rem">
|
||||
<ExternalLink href="https://help.uniswap.org/en/articles/5675203-terms-of-service-faq">
|
||||
<Trans>Learn more</Trans>
|
||||
</ExternalLink>
|
||||
</Row>
|
||||
</ThemedText.Body>
|
||||
</AutoColumn>
|
||||
</AutoColumn>
|
||||
</Wrapper>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { ButtonOutlined } from 'components/Button'
|
||||
import { AutoRow } from 'components/Row'
|
||||
import React from 'react'
|
||||
import ReactGA from 'react-ga4'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
|
||||
@@ -20,7 +20,7 @@ export default function PresetsButtons({ setFullRange }: { setFullRange: () => v
|
||||
<Button
|
||||
onClick={() => {
|
||||
setFullRange()
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Liquidity',
|
||||
action: 'Full Range Clicked',
|
||||
})
|
||||
|
||||
@@ -5,8 +5,8 @@ import Badge from 'components/Badge'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import DoubleCurrencyLogo from 'components/DoubleLogo'
|
||||
import Row, { AutoRow } from 'components/Row'
|
||||
import { RoutingDiagramEntry } from 'components/swap/SwapRoute'
|
||||
import { useTokenInfoFromActiveList } from 'hooks/useTokenInfoFromActiveList'
|
||||
import { RoutingDiagramEntry } from 'lib/components/Swap/RoutingDiagram/utils'
|
||||
import { Box } from 'rebass'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText, Z_INDEX } from 'theme'
|
||||
|
||||
@@ -49,6 +49,8 @@ export default function CommonBases({
|
||||
const isSelected = selectedCurrency?.equals(currency)
|
||||
return (
|
||||
<BaseWrapper
|
||||
tabIndex={0}
|
||||
onKeyPress={(e) => !isSelected && e.key === 'Enter' && onSelect(currency)}
|
||||
onClick={() => !isSelected && onSelect(currency)}
|
||||
disable={isSelected}
|
||||
key={currencyId(currency)}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders currency rows correctly when currencies list is non-empty 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
style="position: relative; height: 10px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
||||
>
|
||||
<div
|
||||
style="height: 168px; width: 100%;"
|
||||
>
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 Row__RowBetween-sc-u7azg8-1 styleds__MenuItem-sc-muzgnq-3 lmTMKd hLLNig hzJkYd firMKT token-item-0x6B175474E89094C44Da98b954EedeAC495271d0F"
|
||||
style="position: absolute; left: 0px; top: 0px; height: 56px; width: 100%;"
|
||||
tabindex="0"
|
||||
>
|
||||
CurrencyLogo currency=DAI
|
||||
<div
|
||||
class="Column-sc-1r2yyln-0 cYEAJI"
|
||||
>
|
||||
<div
|
||||
class="css-8mokm4"
|
||||
title="Dai Stablecoin"
|
||||
>
|
||||
DAI
|
||||
</div>
|
||||
<div
|
||||
class="theme__TextWrapper-sc-5lu8um-0 gVIOIC css-165qfk5"
|
||||
>
|
||||
Dai Stablecoin
|
||||
</div>
|
||||
</div>
|
||||
<span />
|
||||
</div>
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 Row__RowBetween-sc-u7azg8-1 styleds__MenuItem-sc-muzgnq-3 lmTMKd hLLNig hzJkYd firMKT token-item-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
style="position: absolute; left: 0px; top: 56px; height: 56px; width: 100%;"
|
||||
tabindex="0"
|
||||
>
|
||||
CurrencyLogo currency=USDC
|
||||
<div
|
||||
class="Column-sc-1r2yyln-0 cYEAJI"
|
||||
>
|
||||
<div
|
||||
class="css-8mokm4"
|
||||
title="USD//C"
|
||||
>
|
||||
USDC
|
||||
</div>
|
||||
<div
|
||||
class="theme__TextWrapper-sc-5lu8um-0 gVIOIC css-165qfk5"
|
||||
>
|
||||
USD//C
|
||||
</div>
|
||||
</div>
|
||||
<span />
|
||||
</div>
|
||||
<div
|
||||
class="sc-bdnxRM Row-sc-u7azg8-0 Row__RowBetween-sc-u7azg8-1 styleds__MenuItem-sc-muzgnq-3 lmTMKd hLLNig hzJkYd firMKT token-item-0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
|
||||
style="position: absolute; left: 0px; top: 112px; height: 56px; width: 100%;"
|
||||
tabindex="0"
|
||||
>
|
||||
CurrencyLogo currency=WBTC
|
||||
<div
|
||||
class="Column-sc-1r2yyln-0 cYEAJI"
|
||||
>
|
||||
<div
|
||||
class="css-8mokm4"
|
||||
title="Wrapped BTC"
|
||||
>
|
||||
WBTC
|
||||
</div>
|
||||
<div
|
||||
class="theme__TextWrapper-sc-5lu8um-0 gVIOIC css-165qfk5"
|
||||
>
|
||||
Wrapped BTC
|
||||
</div>
|
||||
</div>
|
||||
<span />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`renders loading rows when isLoading is true 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
style="position: relative; height: 10px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
||||
>
|
||||
<div
|
||||
style="height: 0px; width: 100%;"
|
||||
/>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
73
src/components/SearchModal/CurrencyList/index.test.tsx
Normal file
73
src/components/SearchModal/CurrencyList/index.test.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import { Currency, CurrencyAmount as mockCurrencyAmount, Token as mockToken } from '@uniswap/sdk-core'
|
||||
import { DAI, USDC_MAINNET, WBTC } from 'constants/tokens'
|
||||
import * as mockJSBI from 'jsbi'
|
||||
import { render } from 'test-utils'
|
||||
|
||||
import CurrencyList from '.'
|
||||
|
||||
const noOp = function () {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
const mockCurrencyAmt = {
|
||||
[DAI.address]: mockCurrencyAmount.fromRawAmount(DAI, mockJSBI.default.BigInt(100)),
|
||||
[USDC_MAINNET.address]: mockCurrencyAmount.fromRawAmount(USDC_MAINNET, mockJSBI.default.BigInt(10)),
|
||||
[WBTC.address]: mockCurrencyAmount.fromRawAmount(WBTC, mockJSBI.default.BigInt(1)),
|
||||
}
|
||||
|
||||
jest.mock(
|
||||
'components/CurrencyLogo',
|
||||
() =>
|
||||
({ currency }: { currency: Currency }) =>
|
||||
`CurrencyLogo currency=${currency.symbol}`
|
||||
)
|
||||
|
||||
jest.mock('hooks/useActiveWeb3React', () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
default: () => ({
|
||||
account: '123',
|
||||
active: true,
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
jest.mock('../../../state/wallet/hooks', () => {
|
||||
return {
|
||||
useCurrencyBalance: (currency: Currency) => {
|
||||
return mockCurrencyAmt[(currency as mockToken).address]
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
it('renders loading rows when isLoading is true', () => {
|
||||
const { asFragment } = render(
|
||||
<CurrencyList
|
||||
height={10}
|
||||
currencies={[]}
|
||||
otherListTokens={[]}
|
||||
selectedCurrency={null}
|
||||
onCurrencySelect={noOp}
|
||||
showImportView={noOp}
|
||||
setImportToken={noOp}
|
||||
isLoading={true}
|
||||
/>
|
||||
)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('renders currency rows correctly when currencies list is non-empty', () => {
|
||||
const { asFragment } = render(
|
||||
<CurrencyList
|
||||
height={10}
|
||||
currencies={[DAI, USDC_MAINNET, WBTC]}
|
||||
otherListTokens={[]}
|
||||
selectedCurrency={null}
|
||||
onCurrencySelect={noOp}
|
||||
showImportView={noOp}
|
||||
setImportToken={noOp}
|
||||
isLoading={false}
|
||||
/>
|
||||
)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
})
|
||||
@@ -9,20 +9,20 @@ import { FixedSizeList } from 'react-window'
|
||||
import { Text } from 'rebass'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import TokenListLogo from '../../assets/svg/tokenlist.svg'
|
||||
import { useIsUserAddedToken } from '../../hooks/Tokens'
|
||||
import { useCombinedActiveList } from '../../state/lists/hooks'
|
||||
import { WrappedTokenInfo } from '../../state/lists/wrappedTokenInfo'
|
||||
import { useCurrencyBalance } from '../../state/wallet/hooks'
|
||||
import { ThemedText } from '../../theme'
|
||||
import { isTokenOnList } from '../../utils'
|
||||
import Column from '../Column'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import Loader from '../Loader'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
import { MouseoverTooltip } from '../Tooltip'
|
||||
import ImportRow from './ImportRow'
|
||||
import { MenuItem } from './styleds'
|
||||
import TokenListLogo from '../../../assets/svg/tokenlist.svg'
|
||||
import { useIsUserAddedToken } from '../../../hooks/Tokens'
|
||||
import { useCombinedActiveList } from '../../../state/lists/hooks'
|
||||
import { WrappedTokenInfo } from '../../../state/lists/wrappedTokenInfo'
|
||||
import { useCurrencyBalance } from '../../../state/wallet/hooks'
|
||||
import { ThemedText } from '../../../theme'
|
||||
import { isTokenOnList } from '../../../utils'
|
||||
import Column from '../../Column'
|
||||
import CurrencyLogo from '../../CurrencyLogo'
|
||||
import Loader from '../../Loader'
|
||||
import { RowBetween, RowFixed } from '../../Row'
|
||||
import { MouseoverTooltip } from '../../Tooltip'
|
||||
import ImportRow from '../ImportRow'
|
||||
import { LoadingRows, MenuItem } from '../styleds'
|
||||
|
||||
function currencyKey(currency: Currency): string {
|
||||
return currency.isToken ? currency.address : 'ETHER'
|
||||
@@ -124,8 +124,10 @@ function CurrencyRow({
|
||||
// only show add or remove buttons if not on selected list
|
||||
return (
|
||||
<MenuItem
|
||||
tabIndex={0}
|
||||
style={style}
|
||||
className={`token-item-${key}`}
|
||||
onKeyPress={(e) => (!isSelected && e.key === 'Enter' ? onSelect() : null)}
|
||||
onClick={() => (isSelected ? null : onSelect())}
|
||||
disabled={isSelected}
|
||||
selected={otherSelected}
|
||||
@@ -195,6 +197,7 @@ export default function CurrencyList({
|
||||
showImportView,
|
||||
setImportToken,
|
||||
showCurrencyAmount,
|
||||
isLoading,
|
||||
}: {
|
||||
height: number
|
||||
currencies: Currency[]
|
||||
@@ -206,6 +209,7 @@ export default function CurrencyList({
|
||||
showImportView: () => void
|
||||
setImportToken: (token: Token) => void
|
||||
showCurrencyAmount?: boolean
|
||||
isLoading: boolean
|
||||
}) {
|
||||
const itemData: (Currency | BreakLine)[] = useMemo(() => {
|
||||
if (otherListTokens && otherListTokens?.length > 0) {
|
||||
@@ -232,7 +236,15 @@ export default function CurrencyList({
|
||||
|
||||
const showImport = index > currencies.length
|
||||
|
||||
if (showImport && token) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<LoadingRows>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</LoadingRows>
|
||||
)
|
||||
} else if (showImport && token) {
|
||||
return (
|
||||
<ImportRow style={style} token={token} showImportView={showImportView} setImportToken={setImportToken} dim />
|
||||
)
|
||||
@@ -259,6 +271,7 @@ export default function CurrencyList({
|
||||
setImportToken,
|
||||
showImportView,
|
||||
showCurrencyAmount,
|
||||
isLoading,
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { Currency, Token } from '@uniswap/sdk-core'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import useDebounce from 'hooks/useDebounce'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
@@ -11,7 +12,6 @@ import { getTokenFilter } from 'lib/hooks/useTokenList/filtering'
|
||||
import { tokenComparator, useSortTokensByQuery } from 'lib/hooks/useTokenList/sorting'
|
||||
import { KeyboardEvent, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Edit } from 'react-feather'
|
||||
import ReactGA from 'react-ga4'
|
||||
import AutoSizer from 'react-virtualized-auto-sizer'
|
||||
import { FixedSizeList } from 'react-window'
|
||||
import { Text } from 'rebass'
|
||||
@@ -74,6 +74,8 @@ export function CurrencySearch({
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const theme = useTheme()
|
||||
|
||||
const [tokenLoaderTimerElapsed, setTokenLoaderTimerElapsed] = useState(false)
|
||||
|
||||
// refs for fixed size lists
|
||||
const fixedList = useRef<FixedSizeList>()
|
||||
|
||||
@@ -91,7 +93,7 @@ export function CurrencySearch({
|
||||
|
||||
useEffect(() => {
|
||||
if (isAddressSearch) {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Currency Select',
|
||||
action: 'Search by address',
|
||||
label: isAddressSearch,
|
||||
@@ -103,10 +105,11 @@ export function CurrencySearch({
|
||||
return Object.values(allTokens).filter(getTokenFilter(debouncedQuery))
|
||||
}, [allTokens, debouncedQuery])
|
||||
|
||||
const balances = useAllTokenBalances()
|
||||
const [balances, balancesIsLoading] = useAllTokenBalances()
|
||||
const sortedTokens: Token[] = useMemo(() => {
|
||||
return filteredTokens.sort(tokenComparator.bind(null, balances))
|
||||
}, [balances, filteredTokens])
|
||||
void balancesIsLoading // creates a new array once balances load to update hooks
|
||||
return [...filteredTokens].sort(tokenComparator.bind(null, balances))
|
||||
}, [balances, filteredTokens, balancesIsLoading])
|
||||
|
||||
const filteredSortedTokens = useSortTokensByQuery(debouncedQuery, sortedTokens)
|
||||
|
||||
@@ -173,6 +176,14 @@ export function CurrencySearch({
|
||||
filteredTokens.length === 0 || (debouncedQuery.length > 2 && !isAddressSearch) ? debouncedQuery : undefined
|
||||
)
|
||||
|
||||
// Timeout token loader after 3 seconds to avoid hanging in a loading state.
|
||||
useEffect(() => {
|
||||
const tokenLoaderTimer = setTimeout(() => {
|
||||
setTokenLoaderTimerElapsed(true)
|
||||
}, 3000)
|
||||
return () => clearTimeout(tokenLoaderTimer)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<ContentWrapper>
|
||||
<PaddedColumn gap="16px">
|
||||
@@ -218,6 +229,7 @@ export function CurrencySearch({
|
||||
showImportView={showImportView}
|
||||
setImportToken={setImportToken}
|
||||
showCurrencyAmount={showCurrencyAmount}
|
||||
isLoading={balancesIsLoading && !tokenLoaderTimerElapsed}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { TokenList } from '@uniswap/token-lists'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { ButtonPrimary } from 'components/Button'
|
||||
import Card from 'components/Card'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
@@ -11,7 +12,6 @@ import useTheme from 'hooks/useTheme'
|
||||
import { transparentize } from 'polished'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { AlertTriangle, ArrowLeft } from 'react-feather'
|
||||
import ReactGA from 'react-ga4'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
import { enableList, removeList } from 'state/lists/actions'
|
||||
import { useAllLists } from 'state/lists/hooks'
|
||||
@@ -54,7 +54,7 @@ export function ImportList({ listURL, list, setModalView, onDismiss }: ImportPro
|
||||
setAddError(null)
|
||||
fetchList(listURL)
|
||||
.then(() => {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Lists',
|
||||
action: 'Add List',
|
||||
label: listURL,
|
||||
@@ -66,7 +66,7 @@ export function ImportList({ listURL, list, setModalView, onDismiss }: ImportPro
|
||||
setModalView(CurrencyModalView.manage)
|
||||
})
|
||||
.catch((error) => {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Lists',
|
||||
action: 'Add List Failed',
|
||||
label: listURL,
|
||||
|
||||
@@ -63,7 +63,7 @@ export default function ImportRow({
|
||||
const list = token instanceof WrappedTokenInfo ? token.list : undefined
|
||||
|
||||
return (
|
||||
<TokenSection style={style}>
|
||||
<TokenSection tabIndex={0} style={style}>
|
||||
<CurrencyLogo currency={token} size={'24px'} style={{ opacity: dim ? '0.6' : '1' }} />
|
||||
<AutoColumn gap="4px" style={{ opacity: dim ? '0.6' : '1' }}>
|
||||
<AutoRow>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { TokenList } from '@uniswap/token-lists'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import Card from 'components/Card'
|
||||
import { UNSUPPORTED_LIST_URLS } from 'constants/lists'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
@@ -9,7 +10,6 @@ import parseENSAddress from 'lib/utils/parseENSAddress'
|
||||
import uriToHttp from 'lib/utils/uriToHttp'
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { CheckCircle, Settings } from 'react-feather'
|
||||
import ReactGA from 'react-ga4'
|
||||
import { usePopper } from 'react-popper'
|
||||
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
@@ -26,7 +26,7 @@ import { ButtonEmpty, ButtonPrimary } from '../Button'
|
||||
import Column, { AutoColumn } from '../Column'
|
||||
import ListLogo from '../ListLogo'
|
||||
import Row, { RowBetween, RowFixed } from '../Row'
|
||||
import ListToggle from '../Toggle/ListToggle'
|
||||
import Toggle from '../Toggle'
|
||||
import { CurrencyModalView } from './CurrencySearchModal'
|
||||
import { PaddedColumn, SearchInput, Separator, SeparatorDark } from './styleds'
|
||||
|
||||
@@ -126,7 +126,7 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
|
||||
|
||||
const handleAcceptListUpdate = useCallback(() => {
|
||||
if (!pending) return
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Lists',
|
||||
action: 'Update List from List Select',
|
||||
label: listUrl,
|
||||
@@ -135,13 +135,13 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
|
||||
}, [dispatch, listUrl, pending])
|
||||
|
||||
const handleRemoveList = useCallback(() => {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Lists',
|
||||
action: 'Start Remove List',
|
||||
label: listUrl,
|
||||
})
|
||||
if (window.prompt(t`Please confirm you would like to remove this list by typing REMOVE`) === `REMOVE`) {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Lists',
|
||||
action: 'Confirm Remove List',
|
||||
label: listUrl,
|
||||
@@ -151,7 +151,7 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
|
||||
}, [dispatch, listUrl])
|
||||
|
||||
const handleEnableList = useCallback(() => {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Lists',
|
||||
action: 'Enable List',
|
||||
label: listUrl,
|
||||
@@ -160,7 +160,7 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
|
||||
}, [dispatch, listUrl])
|
||||
|
||||
const handleDisableList = useCallback(() => {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Lists',
|
||||
action: 'Disable List',
|
||||
label: listUrl,
|
||||
@@ -215,7 +215,7 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
|
||||
</StyledMenu>
|
||||
</RowFixed>
|
||||
</Column>
|
||||
<ListToggle
|
||||
<Toggle
|
||||
isActive={isActive}
|
||||
bgColor={listColor}
|
||||
toggle={() => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { LoadingRows as BaseLoadingRows } from 'components/Loader/styled'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { AutoColumn } from '../Column'
|
||||
@@ -72,3 +73,25 @@ export const SeparatorDark = styled.div`
|
||||
height: 1px;
|
||||
background-color: ${({ theme }) => theme.bg3};
|
||||
`
|
||||
|
||||
export const LoadingRows = styled(BaseLoadingRows)`
|
||||
grid-column-gap: 0.5em;
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
max-width: 960px;
|
||||
padding: 12px 20px;
|
||||
|
||||
& > div:nth-child(4n + 1) {
|
||||
grid-column: 1 / 8;
|
||||
height: 1em;
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
& > div:nth-child(4n + 2) {
|
||||
grid-column: 12;
|
||||
height: 1em;
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
& > div:nth-child(4n + 3) {
|
||||
grid-column: 1 / 4;
|
||||
height: 0.75em;
|
||||
}
|
||||
`
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { AUTO_ROUTER_SUPPORTED_CHAINS } from 'lib/hooks/routing/clientSideSmartOrderRouter'
|
||||
import { useContext, useRef, useState } from 'react'
|
||||
import { Settings, X } from 'react-feather'
|
||||
import ReactGA from 'react-ga4'
|
||||
import { Text } from 'rebass'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
|
||||
@@ -211,7 +211,7 @@ export default function SettingsTab({ placeholderSlippage }: { placeholderSlippa
|
||||
id="toggle-optimized-router-button"
|
||||
isActive={!clientSideRouter}
|
||||
toggle={() => {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Routing',
|
||||
action: clientSideRouter ? 'enable routing API' : 'disable routing API',
|
||||
})
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { ThemedText } from '../../theme'
|
||||
|
||||
const Wrapper = styled.button<{ isActive?: boolean; activeElement?: boolean }>`
|
||||
border-radius: 20px;
|
||||
border: none;
|
||||
background: ${({ theme }) => theme.bg1};
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
padding: 0.4rem 0.4rem;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const ToggleElement = styled.span<{ isActive?: boolean; bgColor?: string }>`
|
||||
border-radius: 50%;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
background-color: ${({ isActive, bgColor, theme }) => (isActive ? bgColor : theme.bg4)};
|
||||
:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
`
|
||||
|
||||
const StatusText = styled(ThemedText.Main)<{ isActive?: boolean }>`
|
||||
margin: 0 10px;
|
||||
width: 24px;
|
||||
color: ${({ theme, isActive }) => (isActive ? theme.text1 : theme.text3)};
|
||||
`
|
||||
|
||||
interface ToggleProps {
|
||||
id?: string
|
||||
isActive: boolean
|
||||
bgColor: string
|
||||
toggle: () => void
|
||||
}
|
||||
|
||||
export default function ListToggle({ id, isActive, bgColor, toggle }: ToggleProps) {
|
||||
return (
|
||||
<Wrapper id={id} isActive={isActive} onClick={toggle}>
|
||||
{isActive && (
|
||||
<StatusText fontWeight="600" margin="0 6px" isActive={true}>
|
||||
<Trans>ON</Trans>
|
||||
</StatusText>
|
||||
)}
|
||||
<ToggleElement isActive={isActive} bgColor={bgColor} />
|
||||
{!isActive && (
|
||||
<StatusText fontWeight="600" margin="0 6px" isActive={false}>
|
||||
<Trans>OFF</Trans>
|
||||
</StatusText>
|
||||
)}
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
@@ -1,57 +1,85 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { darken } from 'polished'
|
||||
import { ReactNode } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { useState } from 'react'
|
||||
import styled, { keyframes } from 'styled-components/macro'
|
||||
|
||||
const ToggleElement = styled.span<{ isActive?: boolean; isOnSwitch?: boolean }>`
|
||||
padding: 0.25rem 0.6rem;
|
||||
border-radius: 9px;
|
||||
background: ${({ theme, isActive, isOnSwitch }) => (isActive ? (isOnSwitch ? theme.primary1 : theme.bg4) : 'none')};
|
||||
color: ${({ theme, isActive }) => (isActive ? theme.white : theme.text2)};
|
||||
font-size: 14px;
|
||||
font-weight: ${({ isOnSwitch }) => (isOnSwitch ? '500' : '400')};
|
||||
:hover {
|
||||
user-select: ${({ isOnSwitch }) => (isOnSwitch ? 'none' : 'initial')};
|
||||
background: ${({ theme, isActive, isOnSwitch }) =>
|
||||
isActive ? (isOnSwitch ? darken(0.05, theme.primary1) : darken(0.05, theme.bg4)) : 'none'};
|
||||
color: ${({ theme, isActive, isOnSwitch }) => (isActive ? (isOnSwitch ? theme.white : theme.white) : theme.text3)};
|
||||
const Wrapper = styled.button<{ isActive?: boolean; activeElement?: boolean }>`
|
||||
align-items: center;
|
||||
background: ${({ theme }) => theme.bg1};
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
outline: none;
|
||||
padding: 0.4rem 0.4rem;
|
||||
width: fit-content;
|
||||
`
|
||||
|
||||
const turnOnToggle = keyframes`
|
||||
from {
|
||||
margin-left: 0em;
|
||||
margin-right: 2.2em;
|
||||
}
|
||||
to {
|
||||
margin-left: 2.2em;
|
||||
margin-right: 0em;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledToggle = styled.button<{ isActive?: boolean; activeElement?: boolean }>`
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
background: ${({ theme }) => theme.bg0};
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
padding: 2px;
|
||||
const turnOffToggle = keyframes`
|
||||
from {
|
||||
margin-left: 2.2em;
|
||||
margin-right: 0em;
|
||||
}
|
||||
to {
|
||||
margin-left: 0em;
|
||||
margin-right: 2.2em;
|
||||
}
|
||||
`
|
||||
|
||||
const ToggleElementHoverStyle = (hasBgColor: boolean, theme: any, isActive?: boolean) =>
|
||||
hasBgColor
|
||||
? {
|
||||
opacity: '0.8',
|
||||
}
|
||||
: {
|
||||
background: isActive ? darken(0.05, theme.primary1) : darken(0.05, theme.bg4),
|
||||
color: isActive ? theme.white : theme.text3,
|
||||
}
|
||||
|
||||
const ToggleElement = styled.span<{ isActive?: boolean; bgColor?: string; isInitialToggleLoad?: boolean }>`
|
||||
animation: 0.1s
|
||||
${({ isActive, isInitialToggleLoad }) => (isInitialToggleLoad ? 'none' : isActive ? turnOnToggle : turnOffToggle)}
|
||||
ease-in;
|
||||
background: ${({ theme, bgColor, isActive }) =>
|
||||
isActive ? bgColor ?? theme.primary1 : !!bgColor ? theme.bg4 : theme.text3};
|
||||
border-radius: 50%;
|
||||
height: 24px;
|
||||
:hover {
|
||||
${({ bgColor, theme, isActive }) => ToggleElementHoverStyle(!!bgColor, theme, isActive)}
|
||||
}
|
||||
margin-left: ${({ isActive }) => (isActive ? '2.2em' : '0em')};
|
||||
margin-right: ${({ isActive }) => (!isActive ? '2.2em' : '0em')};
|
||||
width: 24px;
|
||||
`
|
||||
|
||||
interface ToggleProps {
|
||||
id?: string
|
||||
bgColor?: string
|
||||
isActive: boolean
|
||||
toggle: () => void
|
||||
checked?: ReactNode
|
||||
unchecked?: ReactNode
|
||||
}
|
||||
|
||||
export default function Toggle({
|
||||
id,
|
||||
isActive,
|
||||
toggle,
|
||||
checked = <Trans>On</Trans>,
|
||||
unchecked = <Trans>Off</Trans>,
|
||||
}: ToggleProps) {
|
||||
export default function Toggle({ id, bgColor, isActive, toggle }: ToggleProps) {
|
||||
const [isInitialToggleLoad, setIsInitialToggleLoad] = useState(true)
|
||||
|
||||
const switchToggle = () => {
|
||||
toggle()
|
||||
if (isInitialToggleLoad) setIsInitialToggleLoad(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledToggle id={id} isActive={isActive} onClick={toggle}>
|
||||
<ToggleElement isActive={isActive} isOnSwitch={true}>
|
||||
{checked}
|
||||
</ToggleElement>
|
||||
<ToggleElement isActive={!isActive} isOnSwitch={false}>
|
||||
{unchecked}
|
||||
</ToggleElement>
|
||||
</StyledToggle>
|
||||
<Wrapper id={id} isActive={isActive} onClick={switchToggle}>
|
||||
<ToggleElement isActive={isActive} bgColor={bgColor} isInitialToggleLoad={isInitialToggleLoad} />
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ export const TooltipContainer = styled.div`
|
||||
|
||||
interface TooltipProps extends Omit<PopoverProps, 'content'> {
|
||||
text: ReactNode
|
||||
disableHover?: boolean // disable the hover and content display
|
||||
}
|
||||
|
||||
interface TooltipContentProps extends Omit<PopoverProps, 'content'> {
|
||||
@@ -29,19 +30,20 @@ interface TooltipContentProps extends Omit<PopoverProps, 'content'> {
|
||||
}
|
||||
|
||||
export default function Tooltip({ text, ...rest }: TooltipProps) {
|
||||
return <Popover content={<TooltipContainer>{text}</TooltipContainer>} {...rest} />
|
||||
return <Popover content={text && <TooltipContainer>{text}</TooltipContainer>} {...rest} />
|
||||
}
|
||||
|
||||
function TooltipContent({ content, wrap = false, ...rest }: TooltipContentProps) {
|
||||
return <Popover content={wrap ? <TooltipContainer>{content}</TooltipContainer> : content} {...rest} />
|
||||
}
|
||||
|
||||
export function MouseoverTooltip({ children, ...rest }: Omit<TooltipProps, 'show'>) {
|
||||
/** Standard text tooltip. */
|
||||
export function MouseoverTooltip({ text, disableHover, children, ...rest }: Omit<TooltipProps, 'show'>) {
|
||||
const [show, setShow] = useState(false)
|
||||
const open = useCallback(() => setShow(true), [setShow])
|
||||
const close = useCallback(() => setShow(false), [setShow])
|
||||
return (
|
||||
<Tooltip {...rest} show={show}>
|
||||
<Tooltip {...rest} show={show} text={disableHover ? null : text}>
|
||||
<div onMouseEnter={open} onMouseLeave={close}>
|
||||
{children}
|
||||
</div>
|
||||
@@ -49,6 +51,7 @@ export function MouseoverTooltip({ children, ...rest }: Omit<TooltipProps, 'show
|
||||
)
|
||||
}
|
||||
|
||||
/** Tooltip that displays custom content. */
|
||||
export function MouseoverTooltipContent({
|
||||
content,
|
||||
children,
|
||||
|
||||
23
src/components/TopLevelModals/index.tsx
Normal file
23
src/components/TopLevelModals/index.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import AddressClaimModal from 'components/claim/AddressClaimModal'
|
||||
import ConnectedAccountBlocked from 'components/ConnectedAccountBlocked'
|
||||
import useAccountRiskCheck from 'hooks/useAccountRiskCheck'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useModalOpen, useToggleModal } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
|
||||
export default function TopLevelModals() {
|
||||
const addressClaimOpen = useModalOpen(ApplicationModal.ADDRESS_CLAIM)
|
||||
const addressClaimToggle = useToggleModal(ApplicationModal.ADDRESS_CLAIM)
|
||||
|
||||
const blockedAccountModalOpen = useModalOpen(ApplicationModal.BLOCKED_ACCOUNT)
|
||||
const { account } = useActiveWeb3React()
|
||||
|
||||
useAccountRiskCheck(account)
|
||||
const open = Boolean(blockedAccountModalOpen && account)
|
||||
return (
|
||||
<>
|
||||
<AddressClaimModal isOpen={addressClaimOpen} onDismiss={addressClaimToggle} />
|
||||
<ConnectedAccountBlocked account={account} isOpen={open} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { darken } from 'polished'
|
||||
import { ButtonEmpty, ButtonPrimary } from 'components/Button'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import { AbstractConnector } from 'web3-react-abstract-connector'
|
||||
|
||||
import { injected } from '../../connectors'
|
||||
import { SUPPORTED_WALLETS } from '../../constants/wallet'
|
||||
import Loader from '../Loader'
|
||||
import Option from './Option'
|
||||
|
||||
const PendingSection = styled.div`
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
@@ -18,18 +16,18 @@ const PendingSection = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const StyledLoader = styled(Loader)`
|
||||
margin-right: 1rem;
|
||||
`
|
||||
|
||||
const LoadingMessage = styled.div<{ error?: boolean }>`
|
||||
const LoaderContainer = styled.div`
|
||||
margin: 16px 0;
|
||||
${({ theme }) => theme.flexRowNoWrap};
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
const LoadingMessage = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap};
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 20px;
|
||||
color: ${({ theme, error }) => (error ? theme.red1 : 'inherit')};
|
||||
border: 1px solid ${({ theme, error }) => (error ? theme.red1 : theme.text4)};
|
||||
|
||||
& > * {
|
||||
padding: 1rem;
|
||||
@@ -37,29 +35,13 @@ const LoadingMessage = styled.div<{ error?: boolean }>`
|
||||
`
|
||||
|
||||
const ErrorGroup = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap};
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
`
|
||||
|
||||
const ErrorButton = styled.div`
|
||||
border-radius: 8px;
|
||||
font-size: 12px;
|
||||
color: ${({ theme }) => theme.text1};
|
||||
background-color: ${({ theme }) => theme.bg4};
|
||||
margin-left: 1rem;
|
||||
padding: 0.5rem;
|
||||
font-weight: 600;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: ${({ theme }) => darken(0.1, theme.text4)};
|
||||
}
|
||||
`
|
||||
|
||||
const LoadingWrapper = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap};
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`
|
||||
@@ -69,65 +51,56 @@ export default function PendingView({
|
||||
error = false,
|
||||
setPendingError,
|
||||
tryActivation,
|
||||
resetAccountView,
|
||||
}: {
|
||||
connector?: AbstractConnector
|
||||
error?: boolean
|
||||
setPendingError: (error: boolean) => void
|
||||
tryActivation: (connector: AbstractConnector) => void
|
||||
resetAccountView: () => void
|
||||
}) {
|
||||
const isMetamask = window?.ethereum?.isMetaMask
|
||||
|
||||
return (
|
||||
<PendingSection>
|
||||
<LoadingMessage error={error}>
|
||||
<LoadingMessage>
|
||||
<LoadingWrapper>
|
||||
{error ? (
|
||||
<ErrorGroup>
|
||||
<div>
|
||||
<ThemedText.MediumHeader marginBottom={12}>
|
||||
<Trans>Error connecting</Trans>
|
||||
</div>
|
||||
<ErrorButton
|
||||
</ThemedText.MediumHeader>
|
||||
<ThemedText.Body fontSize={14} marginBottom={36} textAlign="center">
|
||||
<Trans>
|
||||
The connection attempt failed. Please click try again and follow the steps to connect in your wallet.
|
||||
</Trans>
|
||||
</ThemedText.Body>
|
||||
<ButtonPrimary
|
||||
$borderRadius="12px"
|
||||
padding="12px"
|
||||
onClick={() => {
|
||||
setPendingError(false)
|
||||
connector && tryActivation(connector)
|
||||
}}
|
||||
>
|
||||
<Trans>Try Again</Trans>
|
||||
</ErrorButton>
|
||||
</ButtonPrimary>
|
||||
<ButtonEmpty width="fit-content" padding="0" marginTop={20}>
|
||||
<ThemedText.Link fontSize={12} onClick={resetAccountView}>
|
||||
<Trans>Back to wallet selection</Trans>
|
||||
</ThemedText.Link>
|
||||
</ButtonEmpty>
|
||||
</ErrorGroup>
|
||||
) : (
|
||||
<>
|
||||
<StyledLoader />
|
||||
<Trans>Initializing...</Trans>
|
||||
<ThemedText.Black fontSize={20} marginY={16}>
|
||||
<LoaderContainer>
|
||||
<Loader stroke="currentColor" size="32px" />
|
||||
</LoaderContainer>
|
||||
<Trans>Connecting...</Trans>
|
||||
</ThemedText.Black>
|
||||
</>
|
||||
)}
|
||||
</LoadingWrapper>
|
||||
</LoadingMessage>
|
||||
{Object.keys(SUPPORTED_WALLETS).map((key) => {
|
||||
const option = SUPPORTED_WALLETS[key]
|
||||
if (option.connector === connector) {
|
||||
if (option.connector === injected) {
|
||||
if (isMetamask && option.name !== 'MetaMask') {
|
||||
return null
|
||||
}
|
||||
if (!isMetamask && option.name === 'MetaMask') {
|
||||
return null
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Option
|
||||
id={`connect-${key}`}
|
||||
key={key}
|
||||
clickable={false}
|
||||
color={option.color}
|
||||
header={option.name}
|
||||
subheader={option.description}
|
||||
icon={option.iconURL}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return null
|
||||
})}
|
||||
</PendingSection>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { PrivacyPolicy } from 'components/PrivacyPolicy'
|
||||
import Row, { AutoRow, RowBetween } from 'components/Row'
|
||||
import { useWalletConnectMonitoringEventCallback } from 'hooks/useMonitoringEventCallback'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { ArrowLeft, ArrowRight, Info } from 'react-feather'
|
||||
import ReactGA from 'react-ga4'
|
||||
import Row, { AutoRow } from 'components/Row'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { ArrowLeft } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { AbstractConnector } from 'web3-react-abstract-connector'
|
||||
import { UnsupportedChainIdError, useWeb3React } from 'web3-react-core'
|
||||
import { WalletConnectConnector } from 'web3-react-walletconnect-connector'
|
||||
|
||||
import MetamaskIcon from '../../assets/images/metamask.png'
|
||||
import TallyIcon from '../../assets/images/tally.png'
|
||||
import { ReactComponent as Close } from '../../assets/images/x.svg'
|
||||
import { fortmatic, injected } from '../../connectors'
|
||||
import { OVERLAY_READY } from '../../connectors/Fortmatic'
|
||||
@@ -22,7 +22,7 @@ import { ApplicationModal } from '../../state/application/reducer'
|
||||
import { ExternalLink, ThemedText } from '../../theme'
|
||||
import { isMobile } from '../../utils/userAgent'
|
||||
import AccountDetails from '../AccountDetails'
|
||||
import Card, { LightCard } from '../Card'
|
||||
import { LightCard } from '../Card'
|
||||
import Modal from '../Modal'
|
||||
import Option from './Option'
|
||||
import PendingView from './PendingView'
|
||||
@@ -109,16 +109,6 @@ const HoverText = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const LinkCard = styled(Card)`
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
color: ${({ theme }) => theme.text3};
|
||||
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
`
|
||||
|
||||
const WALLET_VIEWS = {
|
||||
OPTIONS: 'options',
|
||||
OPTIONS_SECONDARY: 'options_secondary',
|
||||
@@ -137,7 +127,7 @@ export default function WalletModal({
|
||||
ENSName?: string
|
||||
}) {
|
||||
// important that these are destructed from the account-specific web3-react context
|
||||
const { active, account, connector, activate, error } = useWeb3React()
|
||||
const { account, connector, activate, error } = useWeb3React()
|
||||
|
||||
const [walletView, setWalletView] = useState(WALLET_VIEWS.ACCOUNT)
|
||||
const previousWalletView = usePrevious(walletView)
|
||||
@@ -151,7 +141,10 @@ export default function WalletModal({
|
||||
|
||||
const previousAccount = usePrevious(account)
|
||||
|
||||
const logMonitoringEvent = useWalletConnectMonitoringEventCallback()
|
||||
const resetAccountView = useCallback(() => {
|
||||
setPendingError(false)
|
||||
setWalletView(WALLET_VIEWS.ACCOUNT)
|
||||
}, [setPendingError, setWalletView])
|
||||
|
||||
// close on connection, when logged out before
|
||||
useEffect(() => {
|
||||
@@ -163,19 +156,9 @@ export default function WalletModal({
|
||||
// always reset to account view
|
||||
useEffect(() => {
|
||||
if (walletModalOpen) {
|
||||
setPendingError(false)
|
||||
setWalletView(WALLET_VIEWS.ACCOUNT)
|
||||
resetAccountView()
|
||||
}
|
||||
}, [walletModalOpen])
|
||||
|
||||
// close modal when a connection is successful
|
||||
const activePrevious = usePrevious(active)
|
||||
const connectorPrevious = usePrevious(connector)
|
||||
useEffect(() => {
|
||||
if (walletModalOpen && ((active && !activePrevious) || (connector && connector !== connectorPrevious && !error))) {
|
||||
setWalletView(WALLET_VIEWS.ACCOUNT)
|
||||
}
|
||||
}, [setWalletView, active, error, connector, walletModalOpen, activePrevious, connectorPrevious])
|
||||
}, [walletModalOpen, resetAccountView])
|
||||
|
||||
const tryActivation = async (connector: AbstractConnector | undefined) => {
|
||||
let name = ''
|
||||
@@ -186,7 +169,7 @@ export default function WalletModal({
|
||||
return true
|
||||
})
|
||||
// log selected wallet
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Wallet',
|
||||
action: 'Change Wallet',
|
||||
label: name,
|
||||
@@ -200,18 +183,13 @@ export default function WalletModal({
|
||||
}
|
||||
|
||||
connector &&
|
||||
activate(connector, undefined, true)
|
||||
.then(async () => {
|
||||
const walletAddress = await connector.getAccount()
|
||||
logMonitoringEvent({ walletAddress })
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error instanceof UnsupportedChainIdError) {
|
||||
activate(connector) // a little janky...can't use setError because the connector isn't set
|
||||
} else {
|
||||
setPendingError(true)
|
||||
}
|
||||
})
|
||||
activate(connector, undefined, true).catch((error) => {
|
||||
if (error instanceof UnsupportedChainIdError) {
|
||||
activate(connector) // a little janky...can't use setError because the connector isn't set
|
||||
} else {
|
||||
setPendingError(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// close wallet modal if fortmatic modal is active
|
||||
@@ -223,7 +201,8 @@ export default function WalletModal({
|
||||
|
||||
// get wallets user can switch too, depending on device/browser
|
||||
function getOptions() {
|
||||
const isMetamask = window.ethereum && window.ethereum.isMetaMask
|
||||
const isMetamask = !!window.ethereum?.isMetaMask
|
||||
const isTally = !!window.ethereum?.isTally
|
||||
return Object.keys(SUPPORTED_WALLETS).map((key) => {
|
||||
const option = SUPPORTED_WALLETS[key]
|
||||
// check for mobile options
|
||||
@@ -275,6 +254,24 @@ export default function WalletModal({
|
||||
// likewise for generic
|
||||
else if (option.name === 'Injected' && isMetamask) {
|
||||
return null
|
||||
} else if (option.name === 'Injected' && isTally) {
|
||||
return (
|
||||
<Option
|
||||
id={`connect-${key}`}
|
||||
key={key}
|
||||
onClick={() => {
|
||||
option.connector === connector
|
||||
? setWalletView(WALLET_VIEWS.ACCOUNT)
|
||||
: !option.href && tryActivation(option.connector)
|
||||
}}
|
||||
color={'#E8831D'}
|
||||
header={<Trans>Tally</Trans>}
|
||||
active={option.connector === connector}
|
||||
subheader={null}
|
||||
link={null}
|
||||
icon={TallyIcon}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,12 +363,7 @@ export default function WalletModal({
|
||||
</CloseIcon>
|
||||
{walletView !== WALLET_VIEWS.ACCOUNT ? (
|
||||
<HeaderRow color="blue">
|
||||
<HoverText
|
||||
onClick={() => {
|
||||
setPendingError(false)
|
||||
setWalletView(WALLET_VIEWS.ACCOUNT)
|
||||
}}
|
||||
>
|
||||
<HoverText onClick={resetAccountView}>
|
||||
<ArrowLeft />
|
||||
</HoverText>
|
||||
</HeaderRow>
|
||||
@@ -385,39 +377,38 @@ export default function WalletModal({
|
||||
|
||||
<ContentWrapper>
|
||||
<AutoColumn gap="16px">
|
||||
<LightCard>
|
||||
<AutoRow style={{ flexWrap: 'nowrap' }}>
|
||||
<ThemedText.Black fontSize={14}>
|
||||
<Trans>
|
||||
By connecting a wallet, you agree to Uniswap Labs’{' '}
|
||||
<ExternalLink href="https://uniswap.org/terms-of-service/">Terms of Service</ExternalLink> and
|
||||
acknowledge that you have read and understand the Uniswap{' '}
|
||||
<ExternalLink href="https://uniswap.org/disclaimer/">Protocol Disclaimer</ExternalLink>.
|
||||
</Trans>
|
||||
</ThemedText.Black>
|
||||
</AutoRow>
|
||||
</LightCard>
|
||||
{walletView === WALLET_VIEWS.PENDING ? (
|
||||
{walletView === WALLET_VIEWS.PENDING && (
|
||||
<PendingView
|
||||
connector={pendingWallet}
|
||||
error={pendingError}
|
||||
setPendingError={setPendingError}
|
||||
tryActivation={tryActivation}
|
||||
resetAccountView={resetAccountView}
|
||||
/>
|
||||
) : (
|
||||
<OptionGrid>{getOptions()}</OptionGrid>
|
||||
)}
|
||||
<LinkCard padding=".5rem" $borderRadius=".75rem" onClick={() => setWalletView(WALLET_VIEWS.LEGAL)}>
|
||||
<RowBetween>
|
||||
<AutoRow gap="4px">
|
||||
<Info size={20} />
|
||||
<ThemedText.Label fontSize={14}>
|
||||
<Trans>How this app uses APIs</Trans>
|
||||
</ThemedText.Label>
|
||||
{walletView !== WALLET_VIEWS.PENDING && <OptionGrid>{getOptions()}</OptionGrid>}
|
||||
{!pendingError && (
|
||||
<LightCard>
|
||||
<AutoRow style={{ flexWrap: 'nowrap' }}>
|
||||
<ThemedText.Body fontSize={12}>
|
||||
<Trans>
|
||||
By connecting a wallet, you agree to Uniswap Labs’{' '}
|
||||
<ExternalLink
|
||||
style={{ textDecoration: 'underline' }}
|
||||
href="https://uniswap.org/terms-of-service/"
|
||||
>
|
||||
Terms of Service
|
||||
</ExternalLink>{' '}
|
||||
and acknowledge that you have read and understand the Uniswap{' '}
|
||||
<ExternalLink style={{ textDecoration: 'underline' }} href="https://uniswap.org/disclaimer/">
|
||||
Protocol Disclaimer
|
||||
</ExternalLink>
|
||||
.
|
||||
</Trans>
|
||||
</ThemedText.Body>
|
||||
</AutoRow>
|
||||
<ArrowRight size={16} />
|
||||
</RowBetween>
|
||||
</LinkCard>
|
||||
</LightCard>
|
||||
)}
|
||||
</AutoColumn>
|
||||
</ContentWrapper>
|
||||
</UpperSection>
|
||||
|
||||
@@ -13,7 +13,7 @@ import useENSName from '../../hooks/useENSName'
|
||||
import { useHasSocks } from '../../hooks/useSocksBalance'
|
||||
import { useWalletModalToggle } from '../../state/application/hooks'
|
||||
import { isTransactionRecent, useAllTransactions } from '../../state/transactions/hooks'
|
||||
import { TransactionDetails } from '../../state/transactions/reducer'
|
||||
import { TransactionDetails } from '../../state/transactions/types'
|
||||
import { shortenAddress } from '../../utils'
|
||||
import { ButtonSecondary } from '../Button'
|
||||
import StatusIcon from '../Identicon/StatusIcon'
|
||||
|
||||
51
src/components/analytics/GoogleAnalyticsProvider.tsx
Normal file
51
src/components/analytics/GoogleAnalyticsProvider.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import ReactGA from 'react-ga4'
|
||||
import { GaOptions, InitOptions, UaEventOptions } from 'react-ga4/types/ga4'
|
||||
|
||||
/**
|
||||
* Google Analytics Provider containing all methods used throughout app to log events to Google Analytics.
|
||||
*/
|
||||
export default class GoogleAnalyticsProvider {
|
||||
public sendEvent(event: string | UaEventOptions, params?: any) {
|
||||
ReactGA.event(event, params)
|
||||
}
|
||||
|
||||
public initialize(
|
||||
GA_MEASUREMENT_ID: InitOptions[] | string,
|
||||
options?: {
|
||||
legacyDimensionMetric?: boolean
|
||||
nonce?: string
|
||||
testMode?: boolean
|
||||
gaOptions?: GaOptions | any
|
||||
gtagOptions?: any
|
||||
}
|
||||
) {
|
||||
ReactGA.initialize(GA_MEASUREMENT_ID, options)
|
||||
}
|
||||
|
||||
public set(fieldsObject: any) {
|
||||
ReactGA.set(fieldsObject)
|
||||
}
|
||||
|
||||
public outboundLink(
|
||||
{
|
||||
label,
|
||||
}: {
|
||||
label: string
|
||||
},
|
||||
hitCallback: () => unknown
|
||||
) {
|
||||
ReactGA.outboundLink({ label }, hitCallback)
|
||||
}
|
||||
|
||||
public pageview(path?: string, _?: string[], title?: string) {
|
||||
ReactGA.pageview(path, _, title)
|
||||
}
|
||||
|
||||
public ga(...args: any[]) {
|
||||
ReactGA.ga(...args)
|
||||
}
|
||||
|
||||
public gaCommandSendTiming(timingCategory: any, timingVar: any, timingValue: any, timingLabel: any) {
|
||||
ReactGA._gaCommandSendTiming(timingCategory, timingVar, timingValue, timingLabel)
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useEffect } from 'react'
|
||||
import ReactGA from 'react-ga4'
|
||||
import { RouteComponentProps } from 'react-router-dom'
|
||||
import { getCLS, getFCP, getFID, getLCP, Metric } from 'web-vitals'
|
||||
|
||||
import { GOOGLE_ANALYTICS_CLIENT_ID_STORAGE_KEY } from './index'
|
||||
|
||||
function reportWebVitals({ name, delta, id }: Metric) {
|
||||
ReactGA._gaCommandSendTiming('Web Vitals', name, Math.round(name === 'CLS' ? delta * 1000 : delta), id)
|
||||
}
|
||||
|
||||
// tracks web vitals and pageviews
|
||||
export default function GoogleAnalyticsReporter({ location: { pathname, search } }: RouteComponentProps): null {
|
||||
useEffect(() => {
|
||||
getFCP(reportWebVitals)
|
||||
getFID(reportWebVitals)
|
||||
getLCP(reportWebVitals)
|
||||
getCLS(reportWebVitals)
|
||||
}, [])
|
||||
|
||||
const { chainId } = useActiveWeb3React()
|
||||
useEffect(() => {
|
||||
// cd1 - custom dimension 1 - chainId
|
||||
ReactGA.set({ cd1: chainId ?? 0 })
|
||||
}, [chainId])
|
||||
|
||||
useEffect(() => {
|
||||
ReactGA.pageview(`${pathname}${search}`)
|
||||
}, [pathname, search])
|
||||
|
||||
useEffect(() => {
|
||||
// typed as 'any' in react-ga4 -.-
|
||||
ReactGA.ga((tracker: any) => {
|
||||
if (!tracker) return
|
||||
|
||||
const clientId = tracker.get('clientId')
|
||||
window.localStorage.setItem(GOOGLE_ANALYTICS_CLIENT_ID_STORAGE_KEY, clientId)
|
||||
})
|
||||
}, [])
|
||||
return null
|
||||
}
|
||||
@@ -1,20 +1,47 @@
|
||||
import ReactGA from 'react-ga4'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useEffect } from 'react'
|
||||
import { UaEventOptions } from 'react-ga4/types/ga4'
|
||||
import { RouteComponentProps } from 'react-router-dom'
|
||||
import { isMobile } from 'utils/userAgent'
|
||||
import { getCLS, getFCP, getFID, getLCP, Metric } from 'web-vitals'
|
||||
|
||||
import GoogleAnalyticsProvider from './GoogleAnalyticsProvider'
|
||||
|
||||
export const GOOGLE_ANALYTICS_CLIENT_ID_STORAGE_KEY = 'ga_client_id'
|
||||
const GOOGLE_ANALYTICS_ID: string | undefined = process.env.REACT_APP_GOOGLE_ANALYTICS_ID
|
||||
|
||||
const storedClientId = window.localStorage.getItem(GOOGLE_ANALYTICS_CLIENT_ID_STORAGE_KEY)
|
||||
|
||||
const googleAnalytics = new GoogleAnalyticsProvider()
|
||||
|
||||
export function sendEvent(event: string | UaEventOptions, params?: any) {
|
||||
return googleAnalytics.sendEvent(event, params)
|
||||
}
|
||||
|
||||
export function outboundLink(
|
||||
{
|
||||
label,
|
||||
}: {
|
||||
label: string
|
||||
},
|
||||
hitCallback: () => unknown
|
||||
) {
|
||||
return googleAnalytics.outboundLink({ label }, hitCallback)
|
||||
}
|
||||
|
||||
export function sendTiming(timingCategory: any, timingVar: any, timingValue: any, timingLabel: any) {
|
||||
return googleAnalytics.gaCommandSendTiming(timingCategory, timingVar, timingValue, timingLabel)
|
||||
}
|
||||
|
||||
if (typeof GOOGLE_ANALYTICS_ID === 'string') {
|
||||
ReactGA.initialize(GOOGLE_ANALYTICS_ID, {
|
||||
googleAnalytics.initialize(GOOGLE_ANALYTICS_ID, {
|
||||
gaOptions: {
|
||||
storage: 'none',
|
||||
storeGac: false,
|
||||
clientId: storedClientId ?? undefined,
|
||||
},
|
||||
})
|
||||
ReactGA.set({
|
||||
googleAnalytics.set({
|
||||
anonymizeIp: true,
|
||||
customBrowserType: !isMobile
|
||||
? 'desktop'
|
||||
@@ -23,5 +50,44 @@ if (typeof GOOGLE_ANALYTICS_ID === 'string') {
|
||||
: 'mobileRegular',
|
||||
})
|
||||
} else {
|
||||
ReactGA.initialize('test', { gtagOptions: { debug_mode: true } })
|
||||
googleAnalytics.initialize('test', { gtagOptions: { debug_mode: true } })
|
||||
}
|
||||
|
||||
const installed = Boolean(window.navigator.serviceWorker?.controller)
|
||||
const hit = Boolean((window as any).__isDocumentCached)
|
||||
const action = installed ? (hit ? 'Cache hit' : 'Cache miss') : 'Not installed'
|
||||
sendEvent({ category: 'Service Worker', action, nonInteraction: true })
|
||||
|
||||
function reportWebVitals({ name, delta, id }: Metric) {
|
||||
sendTiming('Web Vitals', name, Math.round(name === 'CLS' ? delta * 1000 : delta), id)
|
||||
}
|
||||
|
||||
// tracks web vitals and pageviews
|
||||
export function useAnalyticsReporter({ pathname, search }: RouteComponentProps['location']) {
|
||||
useEffect(() => {
|
||||
getFCP(reportWebVitals)
|
||||
getFID(reportWebVitals)
|
||||
getLCP(reportWebVitals)
|
||||
getCLS(reportWebVitals)
|
||||
}, [])
|
||||
|
||||
const { chainId } = useActiveWeb3React()
|
||||
useEffect(() => {
|
||||
// cd1 - custom dimension 1 - chainId
|
||||
googleAnalytics.set({ cd1: chainId ?? 0 })
|
||||
}, [chainId])
|
||||
|
||||
useEffect(() => {
|
||||
googleAnalytics.pageview(`${pathname}${search}`)
|
||||
}, [pathname, search])
|
||||
|
||||
useEffect(() => {
|
||||
// typed as 'any' in react-ga4 -.-
|
||||
googleAnalytics.ga((tracker: any) => {
|
||||
if (!tracker) return
|
||||
|
||||
const clientId = tracker.get('clientId')
|
||||
window.localStorage.setItem(GOOGLE_ANALYTICS_CLIENT_ID_STORAGE_KEY, clientId)
|
||||
})
|
||||
}, [])
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import styled from 'styled-components/macro'
|
||||
|
||||
import { useContract } from '../../hooks/useContract'
|
||||
import { StakingInfo } from '../../state/stake/hooks'
|
||||
import { TransactionType } from '../../state/transactions/actions'
|
||||
import { useTransactionAdder } from '../../state/transactions/hooks'
|
||||
import { TransactionType } from '../../state/transactions/types'
|
||||
import { CloseIcon, ThemedText } from '../../theme'
|
||||
import { ButtonError } from '../Button'
|
||||
import { AutoColumn } from '../Column'
|
||||
|
||||
@@ -12,8 +12,8 @@ import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallbac
|
||||
import { useContract, usePairContract, useV2RouterContract } from '../../hooks/useContract'
|
||||
import useTransactionDeadline from '../../hooks/useTransactionDeadline'
|
||||
import { StakingInfo, useDerivedStakeInfo } from '../../state/stake/hooks'
|
||||
import { TransactionType } from '../../state/transactions/actions'
|
||||
import { useTransactionAdder } from '../../state/transactions/hooks'
|
||||
import { TransactionType } from '../../state/transactions/types'
|
||||
import { CloseIcon, ThemedText } from '../../theme'
|
||||
import { formatCurrencyAmount } from '../../utils/formatCurrencyAmount'
|
||||
import { maxAmountSpend } from '../../utils/maxAmountSpend'
|
||||
|
||||
@@ -7,8 +7,8 @@ import styled from 'styled-components/macro'
|
||||
|
||||
import { useContract } from '../../hooks/useContract'
|
||||
import { StakingInfo } from '../../state/stake/hooks'
|
||||
import { TransactionType } from '../../state/transactions/actions'
|
||||
import { useTransactionAdder } from '../../state/transactions/hooks'
|
||||
import { TransactionType } from '../../state/transactions/types'
|
||||
import { CloseIcon, ThemedText } from '../../theme'
|
||||
import { ButtonError } from '../Button'
|
||||
import { AutoColumn } from '../Column'
|
||||
@@ -42,7 +42,7 @@ export default function UnstakingModal({ isOpen, onDismiss, stakingInfo }: Staki
|
||||
const [hash, setHash] = useState<string | undefined>()
|
||||
const [attempting, setAttempting] = useState(false)
|
||||
|
||||
function wrappedOndismiss() {
|
||||
function wrappedOnDismiss() {
|
||||
setHash(undefined)
|
||||
setAttempting(false)
|
||||
onDismiss()
|
||||
@@ -79,14 +79,14 @@ export default function UnstakingModal({ isOpen, onDismiss, stakingInfo }: Staki
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onDismiss={wrappedOndismiss} maxHeight={90}>
|
||||
<Modal isOpen={isOpen} onDismiss={wrappedOnDismiss} maxHeight={90}>
|
||||
{!attempting && !hash && (
|
||||
<ContentWrapper gap="lg">
|
||||
<RowBetween>
|
||||
<ThemedText.MediumHeader>
|
||||
<Trans>Withdraw</Trans>
|
||||
</ThemedText.MediumHeader>
|
||||
<CloseIcon onClick={wrappedOndismiss} />
|
||||
<CloseIcon onClick={wrappedOnDismiss} />
|
||||
</RowBetween>
|
||||
{stakingInfo?.stakedAmount && (
|
||||
<AutoColumn justify="center" gap="md">
|
||||
@@ -117,7 +117,7 @@ export default function UnstakingModal({ isOpen, onDismiss, stakingInfo }: Staki
|
||||
</ContentWrapper>
|
||||
)}
|
||||
{attempting && !hash && (
|
||||
<LoadingView onDismiss={wrappedOndismiss}>
|
||||
<LoadingView onDismiss={wrappedOnDismiss}>
|
||||
<AutoColumn gap="12px" justify={'center'}>
|
||||
<ThemedText.Body fontSize={20}>
|
||||
<Trans>Withdrawing {stakingInfo?.stakedAmount?.toSignificant(4)} UNI-V2</Trans>
|
||||
@@ -129,7 +129,7 @@ export default function UnstakingModal({ isOpen, onDismiss, stakingInfo }: Staki
|
||||
</LoadingView>
|
||||
)}
|
||||
{hash && (
|
||||
<SubmittedView onDismiss={wrappedOndismiss} hash={hash}>
|
||||
<SubmittedView onDismiss={wrappedOnDismiss} hash={hash}>
|
||||
<AutoColumn gap="12px" justify={'center'}>
|
||||
<ThemedText.LargeHeader>
|
||||
<Trans>Transaction Submitted</Trans>
|
||||
|
||||
@@ -4,6 +4,7 @@ import Card from 'components/Card'
|
||||
import { LoadingRows } from 'components/Loader/styled'
|
||||
import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
import { useContext, useMemo } from 'react'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
@@ -12,6 +13,7 @@ import { Separator, ThemedText } from '../../theme'
|
||||
import { computeRealizedLPFeePercent } from '../../utils/prices'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
import { MouseoverTooltip } from '../Tooltip'
|
||||
import FormattedPriceImpact from './FormattedPriceImpact'
|
||||
|
||||
const StyledCard = styled(Card)`
|
||||
@@ -23,6 +25,7 @@ interface AdvancedSwapDetailsProps {
|
||||
allowedSlippage: Percent
|
||||
syncing?: boolean
|
||||
hideRouteDiagram?: boolean
|
||||
hideInfoTooltips?: boolean
|
||||
}
|
||||
|
||||
function TextWithLoadingPlaceholder({
|
||||
@@ -43,9 +46,15 @@ function TextWithLoadingPlaceholder({
|
||||
)
|
||||
}
|
||||
|
||||
export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }: AdvancedSwapDetailsProps) {
|
||||
export function AdvancedSwapDetails({
|
||||
trade,
|
||||
allowedSlippage,
|
||||
syncing = false,
|
||||
hideInfoTooltips = false,
|
||||
}: AdvancedSwapDetailsProps) {
|
||||
const theme = useContext(ThemeContext)
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const nativeCurrency = useNativeCurrency()
|
||||
|
||||
const { expectedOutputAmount, priceImpact } = useMemo(() => {
|
||||
if (!trade) return { expectedOutputAmount: undefined, priceImpact: undefined }
|
||||
@@ -60,9 +69,19 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }:
|
||||
<AutoColumn gap="8px">
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<ThemedText.SubHeader color={theme.text1}>
|
||||
<Trans>Expected Output</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
<MouseoverTooltip
|
||||
text={
|
||||
<Trans>
|
||||
The amount you expect to receive at the current market price. You may receive less or more if the
|
||||
market price changes while your transaction is pending.
|
||||
</Trans>
|
||||
}
|
||||
disableHover={hideInfoTooltips}
|
||||
>
|
||||
<ThemedText.SubHeader color={theme.text1}>
|
||||
<Trans>Expected Output</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
</MouseoverTooltip>
|
||||
</RowFixed>
|
||||
<TextWithLoadingPlaceholder syncing={syncing} width={65}>
|
||||
<ThemedText.Black textAlign="right" fontSize={14}>
|
||||
@@ -74,9 +93,14 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }:
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<ThemedText.SubHeader color={theme.text1}>
|
||||
<Trans>Price Impact</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
<MouseoverTooltip
|
||||
text={<Trans>The impact your trade has on the market price of this pool.</Trans>}
|
||||
disableHover={hideInfoTooltips}
|
||||
>
|
||||
<ThemedText.SubHeader color={theme.text1}>
|
||||
<Trans>Price Impact</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
</MouseoverTooltip>
|
||||
</RowFixed>
|
||||
<TextWithLoadingPlaceholder syncing={syncing} width={50}>
|
||||
<ThemedText.Black textAlign="right" fontSize={14}>
|
||||
@@ -87,14 +111,24 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }:
|
||||
<Separator />
|
||||
<RowBetween>
|
||||
<RowFixed style={{ marginRight: '20px' }}>
|
||||
<ThemedText.SubHeader color={theme.text3}>
|
||||
{trade.tradeType === TradeType.EXACT_INPUT ? (
|
||||
<Trans>Minimum received</Trans>
|
||||
) : (
|
||||
<Trans>Maximum sent</Trans>
|
||||
)}{' '}
|
||||
<Trans>after slippage</Trans> ({allowedSlippage.toFixed(2)}%)
|
||||
</ThemedText.SubHeader>
|
||||
<MouseoverTooltip
|
||||
text={
|
||||
<Trans>
|
||||
The minimum amount you are guaranteed to receive. If the price slips any further, your transaction
|
||||
will revert.
|
||||
</Trans>
|
||||
}
|
||||
disableHover={hideInfoTooltips}
|
||||
>
|
||||
<ThemedText.SubHeader color={theme.text3}>
|
||||
{trade.tradeType === TradeType.EXACT_INPUT ? (
|
||||
<Trans>Minimum received</Trans>
|
||||
) : (
|
||||
<Trans>Maximum sent</Trans>
|
||||
)}{' '}
|
||||
<Trans>after slippage</Trans> ({allowedSlippage.toFixed(2)}%)
|
||||
</ThemedText.SubHeader>
|
||||
</MouseoverTooltip>
|
||||
</RowFixed>
|
||||
<TextWithLoadingPlaceholder syncing={syncing} width={70}>
|
||||
<ThemedText.Black textAlign="right" fontSize={14} color={theme.text3}>
|
||||
@@ -106,9 +140,18 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }:
|
||||
</RowBetween>
|
||||
{!trade?.gasUseEstimateUSD || !chainId || !SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId) ? null : (
|
||||
<RowBetween>
|
||||
<ThemedText.SubHeader color={theme.text3}>
|
||||
<Trans>Network Fee</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
<MouseoverTooltip
|
||||
text={
|
||||
<Trans>
|
||||
The fee paid to miners who process your transaction. This must be paid in {nativeCurrency.symbol}.
|
||||
</Trans>
|
||||
}
|
||||
disableHover={hideInfoTooltips}
|
||||
>
|
||||
<ThemedText.SubHeader color={theme.text3}>
|
||||
<Trans>Network Fee</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
</MouseoverTooltip>
|
||||
<TextWithLoadingPlaceholder syncing={syncing} width={50}>
|
||||
<ThemedText.Black textAlign="right" fontSize={14} color={theme.text3}>
|
||||
~${trade.gasUseEstimateUSD.toFixed(2)}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, TradeType } from '@uniswap/sdk-core'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { LoadingOpacityContainer } from 'components/Loader/styled'
|
||||
import { RowFixed } from 'components/Row'
|
||||
import { MouseoverTooltipContent } from 'components/Tooltip'
|
||||
import ReactGA from 'react-ga4'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
@@ -85,7 +85,7 @@ export default function GasEstimateBadge({
|
||||
}
|
||||
placement="bottom"
|
||||
onOpen={() =>
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Gas',
|
||||
action: 'Gas Details Tooltip Open',
|
||||
})
|
||||
|
||||
@@ -147,7 +147,12 @@ export default function SwapDetailsDropdown({
|
||||
content={
|
||||
<ResponsiveTooltipContainer origin="top right" style={{ padding: '0' }}>
|
||||
<Card padding="12px">
|
||||
<AdvancedSwapDetails trade={trade} allowedSlippage={allowedSlippage} syncing={syncing} />
|
||||
<AdvancedSwapDetails
|
||||
trade={trade}
|
||||
allowedSlippage={allowedSlippage}
|
||||
syncing={syncing}
|
||||
hideInfoTooltips={true}
|
||||
/>
|
||||
</Card>
|
||||
</ResponsiveTooltipContainer>
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, TradeType } from '@uniswap/sdk-core'
|
||||
import { Protocol } from '@uniswap/router-sdk'
|
||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import AnimatedDropdown from 'components/AnimatedDropdown'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { LoadingRows } from 'components/Loader/styled'
|
||||
@@ -8,7 +11,6 @@ import { AutoRow, RowBetween } from 'components/Row'
|
||||
import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import useAutoRouterSupported from 'hooks/useAutoRouterSupported'
|
||||
import { getTokenPath } from 'lib/components/Swap/RoutingDiagram/utils'
|
||||
import { memo, useState } from 'react'
|
||||
import { Plus } from 'react-feather'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
@@ -106,3 +108,41 @@ export default memo(function SwapRoute({ trade, syncing, fixedOpen = false, ...r
|
||||
</Wrapper>
|
||||
)
|
||||
})
|
||||
|
||||
export interface RoutingDiagramEntry {
|
||||
percent: Percent
|
||||
path: [Currency, Currency, FeeAmount][]
|
||||
protocol: Protocol
|
||||
}
|
||||
|
||||
const V2_DEFAULT_FEE_TIER = 3000
|
||||
|
||||
/**
|
||||
* Loops through all routes on a trade and returns an array of diagram entries.
|
||||
*/
|
||||
export function getTokenPath(trade: InterfaceTrade<Currency, Currency, TradeType>): RoutingDiagramEntry[] {
|
||||
return trade.swaps.map(({ route: { path: tokenPath, pools, protocol }, inputAmount, outputAmount }) => {
|
||||
const portion =
|
||||
trade.tradeType === TradeType.EXACT_INPUT
|
||||
? inputAmount.divide(trade.inputAmount)
|
||||
: outputAmount.divide(trade.outputAmount)
|
||||
const percent = new Percent(portion.numerator, portion.denominator)
|
||||
const path: RoutingDiagramEntry['path'] = []
|
||||
for (let i = 0; i < pools.length; i++) {
|
||||
const nextPool = pools[i]
|
||||
const tokenIn = tokenPath[i]
|
||||
const tokenOut = tokenPath[i + 1]
|
||||
const entry: RoutingDiagramEntry['path'][0] = [
|
||||
tokenIn,
|
||||
tokenOut,
|
||||
nextPool instanceof Pair ? V2_DEFAULT_FEE_TIER : nextPool.fee,
|
||||
]
|
||||
path.push(entry)
|
||||
}
|
||||
return {
|
||||
percent,
|
||||
path,
|
||||
protocol,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -33,6 +33,11 @@ export default function TradePrice({ price, showInverted, setShowInverted }: Tra
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
const usdcPrice = useUSDCPrice(showInverted ? price.baseCurrency : price.quoteCurrency)
|
||||
/*
|
||||
* calculate needed amount of decimal prices, for prices between 0.95-1.05 use 4 decimal places
|
||||
*/
|
||||
const p = Number(usdcPrice?.toFixed())
|
||||
const visibleDecimalPlaces = p < 1.05 ? 4 : 2
|
||||
|
||||
let formattedPrice: string
|
||||
try {
|
||||
@@ -60,7 +65,7 @@ export default function TradePrice({ price, showInverted, setShowInverted }: Tra
|
||||
</Text>{' '}
|
||||
{usdcPrice && (
|
||||
<ThemedText.DarkGray>
|
||||
<Trans>(${usdcPrice.toSignificant(6, { groupSeparator: ',' })})</Trans>
|
||||
<Trans>(${usdcPrice.toFixed(visibleDecimalPlaces, { groupSeparator: ',' })})</Trans>
|
||||
</ThemedText.DarkGray>
|
||||
)}
|
||||
</StyledPriceContainer>
|
||||
|
||||
@@ -66,7 +66,7 @@ export default function DelegateModal({ isOpen, onDismiss, title }: VoteModalPro
|
||||
const [attempting, setAttempting] = useState(false)
|
||||
|
||||
// wrapper to reset state on modal close
|
||||
function wrappedOndismiss() {
|
||||
function wrappedOnDismiss() {
|
||||
setHash(undefined)
|
||||
setAttempting(false)
|
||||
onDismiss()
|
||||
@@ -90,13 +90,13 @@ export default function DelegateModal({ isOpen, onDismiss, title }: VoteModalPro
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onDismiss={wrappedOndismiss} maxHeight={90}>
|
||||
<Modal isOpen={isOpen} onDismiss={wrappedOnDismiss} maxHeight={90}>
|
||||
{!attempting && !hash && (
|
||||
<ContentWrapper gap="lg">
|
||||
<AutoColumn gap="lg" justify="center">
|
||||
<RowBetween>
|
||||
<ThemedText.MediumHeader fontWeight={500}>{title}</ThemedText.MediumHeader>
|
||||
<StyledClosed stroke="black" onClick={wrappedOndismiss} />
|
||||
<StyledClosed stroke="black" onClick={wrappedOnDismiss} />
|
||||
</RowBetween>
|
||||
<ThemedText.Body>
|
||||
<Trans>Earned UNI tokens represent voting shares in Uniswap governance.</Trans>
|
||||
@@ -119,7 +119,7 @@ export default function DelegateModal({ isOpen, onDismiss, title }: VoteModalPro
|
||||
</ContentWrapper>
|
||||
)}
|
||||
{attempting && !hash && (
|
||||
<LoadingView onDismiss={wrappedOndismiss}>
|
||||
<LoadingView onDismiss={wrappedOnDismiss}>
|
||||
<AutoColumn gap="12px" justify={'center'}>
|
||||
<ThemedText.LargeHeader>
|
||||
{usingDelegate ? <Trans>Delegating votes</Trans> : <Trans>Unlocking Votes</Trans>}
|
||||
@@ -129,7 +129,7 @@ export default function DelegateModal({ isOpen, onDismiss, title }: VoteModalPro
|
||||
</LoadingView>
|
||||
)}
|
||||
{hash && (
|
||||
<SubmittedView onDismiss={wrappedOndismiss} hash={hash}>
|
||||
<SubmittedView onDismiss={wrappedOnDismiss} hash={hash}>
|
||||
<AutoColumn gap="12px" justify={'center'}>
|
||||
<ThemedText.LargeHeader>
|
||||
<Trans>Transaction Submitted</Trans>
|
||||
|
||||
153
src/components/vote/ExecuteModal.tsx
Normal file
153
src/components/vote/ExecuteModal.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useContext, useState } from 'react'
|
||||
import { ArrowUpCircle, X } from 'react-feather'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
|
||||
import Circle from '../../assets/images/blue-loader.svg'
|
||||
import { useExecuteCallback } from '../../state/governance/hooks'
|
||||
import { CustomLightSpinner, ThemedText } from '../../theme'
|
||||
import { ExternalLink } from '../../theme'
|
||||
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
|
||||
import { ButtonPrimary } from '../Button'
|
||||
import { AutoColumn, ColumnCenter } from '../Column'
|
||||
import Modal from '../Modal'
|
||||
import { RowBetween } from '../Row'
|
||||
|
||||
const ContentWrapper = styled(AutoColumn)`
|
||||
width: 100%;
|
||||
padding: 24px;
|
||||
`
|
||||
|
||||
const StyledClosed = styled(X)`
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
|
||||
const ConfirmOrLoadingWrapper = styled.div`
|
||||
width: 100%;
|
||||
padding: 24px;
|
||||
`
|
||||
|
||||
const ConfirmedIcon = styled(ColumnCenter)`
|
||||
padding: 60px 0;
|
||||
`
|
||||
|
||||
interface ExecuteModalProps {
|
||||
isOpen: boolean
|
||||
onDismiss: () => void
|
||||
proposalId: string | undefined // id for the proposal to execute
|
||||
}
|
||||
|
||||
export default function ExecuteModal({ isOpen, onDismiss, proposalId }: ExecuteModalProps) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const executeCallback = useExecuteCallback()
|
||||
|
||||
// monitor call to help UI loading state
|
||||
const [hash, setHash] = useState<string | undefined>()
|
||||
const [attempting, setAttempting] = useState<boolean>(false)
|
||||
|
||||
// get theme for colors
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
// wrapper to reset state on modal close
|
||||
function wrappedOnDismiss() {
|
||||
setHash(undefined)
|
||||
setAttempting(false)
|
||||
onDismiss()
|
||||
}
|
||||
|
||||
async function onExecute() {
|
||||
setAttempting(true)
|
||||
|
||||
// if callback not returned properly ignore
|
||||
if (!executeCallback) return
|
||||
|
||||
// try delegation and store hash
|
||||
const hash = await executeCallback(proposalId)?.catch((error) => {
|
||||
setAttempting(false)
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
if (hash) {
|
||||
setHash(hash)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onDismiss={wrappedOnDismiss} maxHeight={90}>
|
||||
{!attempting && !hash && (
|
||||
<ContentWrapper gap="lg">
|
||||
<AutoColumn gap="lg" justify="center">
|
||||
<RowBetween>
|
||||
<ThemedText.MediumHeader fontWeight={500}>
|
||||
<Trans>Execute Proposal {proposalId}</Trans>
|
||||
</ThemedText.MediumHeader>
|
||||
<StyledClosed onClick={wrappedOnDismiss} />
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<ThemedText.Body>
|
||||
<Trans>Executing this proposal will enact the calldata on-chain.</Trans>
|
||||
</ThemedText.Body>
|
||||
</RowBetween>
|
||||
<ButtonPrimary onClick={onExecute}>
|
||||
<ThemedText.MediumHeader color="white">
|
||||
<Trans>Execute</Trans>
|
||||
</ThemedText.MediumHeader>
|
||||
</ButtonPrimary>
|
||||
</AutoColumn>
|
||||
</ContentWrapper>
|
||||
)}
|
||||
{attempting && !hash && (
|
||||
<ConfirmOrLoadingWrapper>
|
||||
<RowBetween>
|
||||
<div />
|
||||
<StyledClosed onClick={wrappedOnDismiss} />
|
||||
</RowBetween>
|
||||
<ConfirmedIcon>
|
||||
<CustomLightSpinner src={Circle} alt="loader" size={'90px'} />
|
||||
</ConfirmedIcon>
|
||||
<AutoColumn gap="100px" justify={'center'}>
|
||||
<AutoColumn gap="12px" justify={'center'}>
|
||||
<ThemedText.LargeHeader>
|
||||
<Trans>Executing</Trans>
|
||||
</ThemedText.LargeHeader>
|
||||
</AutoColumn>
|
||||
<ThemedText.SubHeader>
|
||||
<Trans>Confirm this transaction in your wallet</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
</AutoColumn>
|
||||
</ConfirmOrLoadingWrapper>
|
||||
)}
|
||||
{hash && (
|
||||
<ConfirmOrLoadingWrapper>
|
||||
<RowBetween>
|
||||
<div />
|
||||
<StyledClosed onClick={wrappedOnDismiss} />
|
||||
</RowBetween>
|
||||
<ConfirmedIcon>
|
||||
<ArrowUpCircle strokeWidth={0.5} size={90} color={theme.primary1} />
|
||||
</ConfirmedIcon>
|
||||
<AutoColumn gap="100px" justify={'center'}>
|
||||
<AutoColumn gap="12px" justify={'center'}>
|
||||
<ThemedText.LargeHeader>
|
||||
<Trans>Execution Submitted</Trans>
|
||||
</ThemedText.LargeHeader>
|
||||
</AutoColumn>
|
||||
{chainId && (
|
||||
<ExternalLink
|
||||
href={getExplorerLink(chainId, hash, ExplorerDataType.TRANSACTION)}
|
||||
style={{ marginLeft: '4px' }}
|
||||
>
|
||||
<ThemedText.SubHeader>
|
||||
<Trans>View transaction on Explorer</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
</ExternalLink>
|
||||
)}
|
||||
</AutoColumn>
|
||||
</ConfirmOrLoadingWrapper>
|
||||
)}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
153
src/components/vote/QueueModal.tsx
Normal file
153
src/components/vote/QueueModal.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useContext, useState } from 'react'
|
||||
import { ArrowUpCircle, X } from 'react-feather'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
|
||||
import Circle from '../../assets/images/blue-loader.svg'
|
||||
import { useQueueCallback } from '../../state/governance/hooks'
|
||||
import { CustomLightSpinner, ThemedText } from '../../theme'
|
||||
import { ExternalLink } from '../../theme'
|
||||
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
|
||||
import { ButtonPrimary } from '../Button'
|
||||
import { AutoColumn, ColumnCenter } from '../Column'
|
||||
import Modal from '../Modal'
|
||||
import { RowBetween } from '../Row'
|
||||
|
||||
const ContentWrapper = styled(AutoColumn)`
|
||||
width: 100%;
|
||||
padding: 24px;
|
||||
`
|
||||
|
||||
const StyledClosed = styled(X)`
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
|
||||
const ConfirmOrLoadingWrapper = styled.div`
|
||||
width: 100%;
|
||||
padding: 24px;
|
||||
`
|
||||
|
||||
const ConfirmedIcon = styled(ColumnCenter)`
|
||||
padding: 60px 0;
|
||||
`
|
||||
|
||||
interface QueueModalProps {
|
||||
isOpen: boolean
|
||||
onDismiss: () => void
|
||||
proposalId: string | undefined // id for the proposal to queue
|
||||
}
|
||||
|
||||
export default function QueueModal({ isOpen, onDismiss, proposalId }: QueueModalProps) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const queueCallback = useQueueCallback()
|
||||
|
||||
// monitor call to help UI loading state
|
||||
const [hash, setHash] = useState<string | undefined>()
|
||||
const [attempting, setAttempting] = useState<boolean>(false)
|
||||
|
||||
// get theme for colors
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
// wrapper to reset state on modal close
|
||||
function wrappedOnDismiss() {
|
||||
setHash(undefined)
|
||||
setAttempting(false)
|
||||
onDismiss()
|
||||
}
|
||||
|
||||
async function onQueue() {
|
||||
setAttempting(true)
|
||||
|
||||
// if callback not returned properly ignore
|
||||
if (!queueCallback) return
|
||||
|
||||
// try delegation and store hash
|
||||
const hash = await queueCallback(proposalId)?.catch((error) => {
|
||||
setAttempting(false)
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
if (hash) {
|
||||
setHash(hash)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onDismiss={wrappedOnDismiss} maxHeight={90}>
|
||||
{!attempting && !hash && (
|
||||
<ContentWrapper gap="lg">
|
||||
<AutoColumn gap="lg" justify="center">
|
||||
<RowBetween>
|
||||
<ThemedText.MediumHeader fontWeight={500}>
|
||||
<Trans>Queue Proposal {proposalId}</Trans>
|
||||
</ThemedText.MediumHeader>
|
||||
<StyledClosed onClick={wrappedOnDismiss} />
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<ThemedText.Body>
|
||||
<Trans>Adding this proposal to the queue will allow it to be executed, after a delay.</Trans>
|
||||
</ThemedText.Body>
|
||||
</RowBetween>
|
||||
<ButtonPrimary onClick={onQueue}>
|
||||
<ThemedText.MediumHeader color="white">
|
||||
<Trans>Queue</Trans>
|
||||
</ThemedText.MediumHeader>
|
||||
</ButtonPrimary>
|
||||
</AutoColumn>
|
||||
</ContentWrapper>
|
||||
)}
|
||||
{attempting && !hash && (
|
||||
<ConfirmOrLoadingWrapper>
|
||||
<RowBetween>
|
||||
<div />
|
||||
<StyledClosed onClick={wrappedOnDismiss} />
|
||||
</RowBetween>
|
||||
<ConfirmedIcon>
|
||||
<CustomLightSpinner src={Circle} alt="loader" size={'90px'} />
|
||||
</ConfirmedIcon>
|
||||
<AutoColumn gap="100px" justify={'center'}>
|
||||
<AutoColumn gap="12px" justify={'center'}>
|
||||
<ThemedText.LargeHeader>
|
||||
<Trans>Queueing</Trans>
|
||||
</ThemedText.LargeHeader>
|
||||
</AutoColumn>
|
||||
<ThemedText.SubHeader>
|
||||
<Trans>Confirm this transaction in your wallet</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
</AutoColumn>
|
||||
</ConfirmOrLoadingWrapper>
|
||||
)}
|
||||
{hash && (
|
||||
<ConfirmOrLoadingWrapper>
|
||||
<RowBetween>
|
||||
<div />
|
||||
<StyledClosed onClick={wrappedOnDismiss} />
|
||||
</RowBetween>
|
||||
<ConfirmedIcon>
|
||||
<ArrowUpCircle strokeWidth={0.5} size={90} color={theme.primary1} />
|
||||
</ConfirmedIcon>
|
||||
<AutoColumn gap="100px" justify={'center'}>
|
||||
<AutoColumn gap="12px" justify={'center'}>
|
||||
<ThemedText.LargeHeader>
|
||||
<Trans>Transaction Submitted</Trans>
|
||||
</ThemedText.LargeHeader>
|
||||
</AutoColumn>
|
||||
{chainId && (
|
||||
<ExternalLink
|
||||
href={getExplorerLink(chainId, hash, ExplorerDataType.TRANSACTION)}
|
||||
style={{ marginLeft: '4px' }}
|
||||
>
|
||||
<ThemedText.SubHeader>
|
||||
<Trans>View transaction on Explorer</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
</ExternalLink>
|
||||
)}
|
||||
</AutoColumn>
|
||||
</ConfirmOrLoadingWrapper>
|
||||
)}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -45,7 +45,7 @@ interface VoteModalProps {
|
||||
|
||||
export default function VoteModal({ isOpen, onDismiss, proposalId, voteOption }: VoteModalProps) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const { voteCallback } = useVoteCallback()
|
||||
const voteCallback = useVoteCallback()
|
||||
const { votes: availableVotes } = useUserVotes()
|
||||
|
||||
// monitor call to help UI loading state
|
||||
@@ -56,7 +56,7 @@ export default function VoteModal({ isOpen, onDismiss, proposalId, voteOption }:
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
// wrapper to reset state on modal close
|
||||
function wrappedOndismiss() {
|
||||
function wrappedOnDismiss() {
|
||||
setHash(undefined)
|
||||
setAttempting(false)
|
||||
onDismiss()
|
||||
@@ -80,7 +80,7 @@ export default function VoteModal({ isOpen, onDismiss, proposalId, voteOption }:
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onDismiss={wrappedOndismiss} maxHeight={90}>
|
||||
<Modal isOpen={isOpen} onDismiss={wrappedOnDismiss} maxHeight={90}>
|
||||
{!attempting && !hash && (
|
||||
<ContentWrapper gap="lg">
|
||||
<AutoColumn gap="lg" justify="center">
|
||||
@@ -94,7 +94,7 @@ export default function VoteModal({ isOpen, onDismiss, proposalId, voteOption }:
|
||||
<Trans>Vote to abstain on proposal {proposalId}</Trans>
|
||||
)}
|
||||
</ThemedText.MediumHeader>
|
||||
<StyledClosed stroke="black" onClick={wrappedOndismiss} />
|
||||
<StyledClosed onClick={wrappedOnDismiss} />
|
||||
</RowBetween>
|
||||
<ThemedText.LargeHeader>
|
||||
<Trans>{formatCurrencyAmount(availableVotes, 4)} Votes</Trans>
|
||||
@@ -117,7 +117,7 @@ export default function VoteModal({ isOpen, onDismiss, proposalId, voteOption }:
|
||||
<ConfirmOrLoadingWrapper>
|
||||
<RowBetween>
|
||||
<div />
|
||||
<StyledClosed onClick={wrappedOndismiss} />
|
||||
<StyledClosed onClick={wrappedOnDismiss} />
|
||||
</RowBetween>
|
||||
<ConfirmedIcon>
|
||||
<CustomLightSpinner src={Circle} alt="loader" size={'90px'} />
|
||||
@@ -138,7 +138,7 @@ export default function VoteModal({ isOpen, onDismiss, proposalId, voteOption }:
|
||||
<ConfirmOrLoadingWrapper>
|
||||
<RowBetween>
|
||||
<div />
|
||||
<StyledClosed onClick={wrappedOndismiss} />
|
||||
<StyledClosed onClick={wrappedOnDismiss} />
|
||||
</RowBetween>
|
||||
<ConfirmedIcon>
|
||||
<ArrowUpCircle strokeWidth={0.5} size={90} color={theme.primary1} />
|
||||
|
||||
@@ -94,7 +94,7 @@ export const CHAIN_INFO: ChainInfoMap = {
|
||||
[SupportedChainId.OPTIMISM]: {
|
||||
networkType: NetworkType.L2,
|
||||
blockWaitMsBeforeWarning: ms`25m`,
|
||||
bridge: 'https://gateway.optimism.io/?chainId=1',
|
||||
bridge: 'https://app.optimism.io/bridge',
|
||||
defaultListUrl: OPTIMISM_LIST,
|
||||
docs: 'https://optimism.io/',
|
||||
explorer: 'https://optimistic.etherscan.io/',
|
||||
@@ -108,7 +108,7 @@ export const CHAIN_INFO: ChainInfoMap = {
|
||||
[SupportedChainId.OPTIMISTIC_KOVAN]: {
|
||||
networkType: NetworkType.L2,
|
||||
blockWaitMsBeforeWarning: ms`25m`,
|
||||
bridge: 'https://gateway.optimism.io/',
|
||||
bridge: 'https://app.optimism.io/bridge',
|
||||
defaultListUrl: OPTIMISM_LIST,
|
||||
docs: 'https://optimism.io/',
|
||||
explorer: 'https://optimistic.etherscan.io/',
|
||||
|
||||
@@ -46,6 +46,15 @@ export const SUPPORTED_GAS_ESTIMATE_CHAIN_IDS = [
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
]
|
||||
|
||||
/**
|
||||
* Unsupported networks for V2 pool behavior.
|
||||
*/
|
||||
export const UNSUPPORTED_V2POOL_CHAIN_IDS = [
|
||||
SupportedChainId.POLYGON,
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
]
|
||||
|
||||
/**
|
||||
* All the chain IDs that are running the Ethereum protocol.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
GOVERNANCE_ALPHA_V0_ADDRESSES,
|
||||
GOVERNANCE_ALPHA_V1_ADDRESSES,
|
||||
GOVERNANCE_BRAVO_ADDRESSES,
|
||||
TIMELOCK_ADDRESS,
|
||||
UNI_ADDRESS,
|
||||
} from './addresses'
|
||||
@@ -11,7 +12,8 @@ export const COMMON_CONTRACT_NAMES: Record<number, { [address: string]: string }
|
||||
[UNI_ADDRESS[SupportedChainId.MAINNET]]: 'UNI',
|
||||
[TIMELOCK_ADDRESS[SupportedChainId.MAINNET]]: 'Timelock',
|
||||
[GOVERNANCE_ALPHA_V0_ADDRESSES[SupportedChainId.MAINNET]]: 'Governance (V0)',
|
||||
[GOVERNANCE_ALPHA_V1_ADDRESSES[SupportedChainId.MAINNET]]: 'Governance',
|
||||
[GOVERNANCE_ALPHA_V1_ADDRESSES[SupportedChainId.MAINNET]]: 'Governance (V1)',
|
||||
[GOVERNANCE_BRAVO_ADDRESSES[SupportedChainId.MAINNET]]: 'Governance',
|
||||
'0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e': 'ENS Registry',
|
||||
'0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41': 'ENS Public Resolver',
|
||||
},
|
||||
|
||||
@@ -2,3 +2,4 @@ export const UNISWAP_GRANTS_START_BLOCK = 11473815
|
||||
export const BRAVO_START_BLOCK = 13059344
|
||||
export const ONE_BIP_START_BLOCK = 13551293
|
||||
export const POLYGON_START_BLOCK = 13786993
|
||||
export const MOONBEAN_START_BLOCK = 14732457
|
||||
|
||||
33
src/hooks/useAccountRiskCheck.ts
Normal file
33
src/hooks/useAccountRiskCheck.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { useEffect } from 'react'
|
||||
import { ApplicationModal, setOpenModal } from 'state/application/reducer'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
|
||||
export default function useAccountRiskCheck(account: string | null | undefined) {
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
useEffect(() => {
|
||||
if (account) {
|
||||
const headers = new Headers({ 'Content-Type': 'application/json' })
|
||||
fetch('https://screening-worker.uniswap.workers.dev', {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({ address: account }),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
if (data.block) {
|
||||
dispatch(setOpenModal(ApplicationModal.BLOCKED_ACCOUNT))
|
||||
sendEvent({
|
||||
category: 'Address Screening',
|
||||
action: 'blocked',
|
||||
label: account,
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch(setOpenModal(null))
|
||||
})
|
||||
}
|
||||
}, [account, dispatch])
|
||||
}
|
||||
@@ -1,15 +1,10 @@
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import { Web3Provider } from '@ethersproject/providers'
|
||||
import { default as useWidgetsWeb3React } from 'lib/hooks/useActiveWeb3React'
|
||||
import { useWeb3React } from 'web3-react-core'
|
||||
|
||||
import { NetworkContextName } from '../constants/misc'
|
||||
|
||||
export default function useActiveWeb3React() {
|
||||
if (process.env.REACT_APP_IS_WIDGET) {
|
||||
return useWidgetsWeb3React()
|
||||
}
|
||||
|
||||
const interfaceContext = useWeb3React<Web3Provider>()
|
||||
const interfaceNetworkContext = useWeb3React<Web3Provider>(
|
||||
process.env.REACT_APP_IS_WIDGET ? undefined : NetworkContextName
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
|
||||
import { updateUserExpertMode } from '../state/user/actions'
|
||||
import { updateUserExpertMode } from '../state/user/reducer'
|
||||
import useParsedQueryString from './useParsedQueryString'
|
||||
|
||||
export default function ApeModeQueryParamReader(): null {
|
||||
|
||||
@@ -6,8 +6,8 @@ import useSwapApproval, { useSwapApprovalOptimizedTrade } from 'lib/hooks/swap/u
|
||||
import { ApprovalState, useApproval } from 'lib/hooks/useApproval'
|
||||
import { useCallback } from 'react'
|
||||
|
||||
import { TransactionType } from '../state/transactions/actions'
|
||||
import { useHasPendingApproval, useTransactionAdder } from '../state/transactions/hooks'
|
||||
import { TransactionType } from '../state/transactions/types'
|
||||
export { ApprovalState } from 'lib/hooks/useApproval'
|
||||
|
||||
function useGetAndTrackApproval(getApproval: ReturnType<typeof useApproval>[1]) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { skipToken } from '@reduxjs/toolkit/query/react'
|
||||
import { Currency, Token } from '@uniswap/sdk-core'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import useBlockNumber from 'lib/hooks/useBlockNumber'
|
||||
import ms from 'ms.macro'
|
||||
import { useMemo } from 'react'
|
||||
import ReactGA from 'react-ga4'
|
||||
import { useFeeTierDistributionQuery } from 'state/data/enhanced'
|
||||
import { FeeTierDistributionQuery } from 'state/data/generated'
|
||||
|
||||
@@ -112,7 +112,7 @@ function usePoolTVL(token0: Token | undefined, token1: Token | undefined) {
|
||||
}
|
||||
|
||||
if (latestBlock - (_meta?.block?.number ?? 0) > MAX_DATA_BLOCK_AGE) {
|
||||
ReactGA.event('exception', { description: `Graph stale (latest block: ${latestBlock})` })
|
||||
sendEvent('exception', { description: `Graph stale (latest block: ${latestBlock})` })
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { SupportedLocale } from 'constants/locales'
|
||||
import { LocationDescriptor } from 'history'
|
||||
import useParsedQueryString from 'hooks/useParsedQueryString'
|
||||
import { stringify } from 'qs'
|
||||
import { useMemo } from 'react'
|
||||
import ReactGA from 'react-ga4'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
import { useActiveLocale } from './useActiveLocale'
|
||||
@@ -26,7 +26,7 @@ export function useLocationLinkProps(locale: SupportedLocale | null): {
|
||||
search: stringify({ ...qs, lng: locale }),
|
||||
},
|
||||
onClick: () => {
|
||||
ReactGA.event({
|
||||
sendEvent({
|
||||
category: 'Localization',
|
||||
action: 'Switch Locale',
|
||||
label: `${activeLocale} -> ${locale}`,
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
import { TransactionResponse } from '@ethersproject/providers'
|
||||
import { initializeApp } from 'firebase/app'
|
||||
import { getDatabase, push, ref } from 'firebase/database'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useCallback } from 'react'
|
||||
import { TransactionInfo, TransactionType } from 'state/transactions/actions'
|
||||
|
||||
type PartialTransactionResponse = Pick<TransactionResponse, 'hash' | 'v' | 'r' | 's'>
|
||||
|
||||
const SUPPORTED_TRANSACTION_TYPES = [
|
||||
TransactionType.ADD_LIQUIDITY_V2_POOL,
|
||||
TransactionType.ADD_LIQUIDITY_V3_POOL,
|
||||
TransactionType.CREATE_V3_POOL,
|
||||
TransactionType.REMOVE_LIQUIDITY_V3,
|
||||
TransactionType.SWAP,
|
||||
]
|
||||
|
||||
const FIREBASE_API_KEY = process.env.REACT_APP_FIREBASE_KEY
|
||||
const firebaseEnabled = typeof FIREBASE_API_KEY !== 'undefined'
|
||||
if (firebaseEnabled) initializeFirebase()
|
||||
|
||||
function useMonitoringEventCallback() {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
|
||||
return useCallback(
|
||||
async function log(
|
||||
type: string,
|
||||
{
|
||||
transactionResponse,
|
||||
walletAddress,
|
||||
}: { transactionResponse: PartialTransactionResponse; walletAddress: string | undefined }
|
||||
) {
|
||||
if (!firebaseEnabled) return
|
||||
|
||||
const db = getDatabase()
|
||||
|
||||
if (!walletAddress) {
|
||||
console.debug('Wallet address required to log monitoring events.')
|
||||
return
|
||||
}
|
||||
try {
|
||||
push(ref(db, 'trm'), {
|
||||
chainId,
|
||||
origin: window.location.origin,
|
||||
timestamp: Date.now(),
|
||||
tx: transactionResponse,
|
||||
type,
|
||||
walletAddress,
|
||||
})
|
||||
} catch (e) {
|
||||
console.debug('Error adding document: ', e)
|
||||
}
|
||||
},
|
||||
[chainId]
|
||||
)
|
||||
}
|
||||
|
||||
export function useTransactionMonitoringEventCallback() {
|
||||
const { account } = useActiveWeb3React()
|
||||
const log = useMonitoringEventCallback()
|
||||
|
||||
return useCallback(
|
||||
(info: TransactionInfo, transactionResponse: TransactionResponse) => {
|
||||
if (SUPPORTED_TRANSACTION_TYPES.includes(info.type)) {
|
||||
log(TransactionType[info.type], {
|
||||
transactionResponse: (({ hash, v, r, s }: PartialTransactionResponse) => ({ hash, v, r, s }))(
|
||||
transactionResponse
|
||||
),
|
||||
walletAddress: account ?? undefined,
|
||||
})
|
||||
}
|
||||
},
|
||||
[account, log]
|
||||
)
|
||||
}
|
||||
|
||||
export function useWalletConnectMonitoringEventCallback() {
|
||||
const log = useMonitoringEventCallback()
|
||||
|
||||
return useCallback(
|
||||
(walletAddress) => {
|
||||
log('WALLET_CONNECTED', { transactionResponse: { hash: '', r: '', s: '', v: -1 }, walletAddress })
|
||||
},
|
||||
[log]
|
||||
)
|
||||
}
|
||||
|
||||
function initializeFirebase() {
|
||||
initializeApp({
|
||||
apiKey: process.env.REACT_APP_FIREBASE_KEY,
|
||||
authDomain: 'interface-monitoring.firebaseapp.com',
|
||||
databaseURL: 'https://interface-monitoring-default-rtdb.firebaseio.com',
|
||||
projectId: 'interface-monitoring',
|
||||
storageBucket: 'interface-monitoring.appspot.com',
|
||||
messagingSenderId: '968187720053',
|
||||
appId: '1:968187720053:web:acedf72dce629d470be33c',
|
||||
})
|
||||
}
|
||||
@@ -4,8 +4,8 @@ import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { SwapCallbackState, useSwapCallback as useLibSwapCallBack } from 'lib/hooks/swap/useSwapCallback'
|
||||
import { ReactNode, useMemo } from 'react'
|
||||
|
||||
import { TransactionType } from '../state/transactions/actions'
|
||||
import { useTransactionAdder } from '../state/transactions/hooks'
|
||||
import { TransactionType } from '../state/transactions/types'
|
||||
import { currencyId } from '../utils/currencyId'
|
||||
import useENS from './useENS'
|
||||
import { SignatureData } from './useERC20Permit'
|
||||
|
||||
@@ -6,8 +6,8 @@ import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { WRAPPED_NATIVE_CURRENCY } from '../constants/tokens'
|
||||
import { TransactionType } from '../state/transactions/actions'
|
||||
import { useTransactionAdder } from '../state/transactions/hooks'
|
||||
import { TransactionType } from '../state/transactions/types'
|
||||
import { useCurrencyBalance } from '../state/wallet/hooks'
|
||||
import { useWETHContract } from './useContract'
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"extends": ["../../.eslintrc.json"],
|
||||
"plugins": ["better-styled-components"],
|
||||
"rules": {
|
||||
"better-styled-components/sort-declarations-alphabetically": "error",
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"paths": [
|
||||
{
|
||||
"name": "react-feather",
|
||||
"message": "Please import from lib/icons to ensure performant usage."
|
||||
},
|
||||
{
|
||||
"name": "@uniswap/smart-order-router",
|
||||
"message": "Forbidden import; smart-order-router is lazy-loaded."
|
||||
}
|
||||
],
|
||||
"patterns": [
|
||||
{
|
||||
"group": ["styled-components"],
|
||||
"message": "Please import styled from lib/theme to get the correct typings."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
// Use Inter mixin to set font-display: block.
|
||||
@use "@fontsource/inter/scss/mixins" as Inter;
|
||||
@include Inter.fontFace(
|
||||
$fontName: 'Inter',
|
||||
$weight: 400,
|
||||
$display: block,
|
||||
);
|
||||
@include Inter.fontFace(
|
||||
$fontName: 'Inter',
|
||||
$weight: 500,
|
||||
$display: block,
|
||||
);
|
||||
@include Inter.fontFace(
|
||||
$fontName: 'Inter',
|
||||
$weight: 600,
|
||||
$display: block,
|
||||
);
|
||||
@include Inter.fontFaceVariable(
|
||||
$display: block,
|
||||
);
|
||||
|
||||
@import "~@fontsource/ibm-plex-mono/400.css";
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.5 KiB |
@@ -1,10 +0,0 @@
|
||||
<svg width="23" height="20" viewBox="0 0 23 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="gradient" x1="0" y1="0" x2="1" y2="0" gradientTransform="rotate(95)">
|
||||
<stop id="stop1" offset="0" stop-color="#2274E2"/>
|
||||
<stop id="stop1" offset="0.5" stop-color="#2274E2"/>
|
||||
<stop id="stop2" offset="1" stop-color="#3FB672" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M16 16C10 16 9 10 5 10M16 16C16 17.6569 17.3431 19 19 19C20.6569 19 22 17.6569 22 16C22 14.3431 20.6569 13 19 13C17.3431 13 16 14.3431 16 16ZM5 10C9 10 10 4 16 4M5 10H1.5M16 4C16 5.65685 17.3431 7 19 7C20.6569 7 22 5.65685 22 4C22 2.34315 20.6569 1 19 1C17.3431 1 16 2.34315 16 4Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke="url(#gradient)" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 780 B |
@@ -1,4 +0,0 @@
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="10" cy="10" r="10" />
|
||||
<path d="M14 7L8.5 12.5L6 10" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 233 B |
@@ -1,4 +0,0 @@
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" xmlns="http://www.w3.org/2000/svg">
|
||||
<polyline class="left" points="18 15 12 9"></polyline>
|
||||
<polyline class="right" points="12 9 6 15"></polyline>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 238 B |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user