Compare commits

...

59 Commits

Author SHA1 Message Date
Christine Legge
71aebf33db fix: remove unused var (#3736) 2022-04-14 16:26:42 -04:00
Christine Legge
5ff428b04b fix: update widgets README 2022-04-14 16:06:57 -04:00
Mark Carbajal
acb0c2056e chore: Remove Portis (#3693)
* Removed portis

* Removed portis

* Removed portis

* Update src/components/WalletModal/index.tsx

Co-authored-by: Bruno Crosier <bruno.crosier@gmail.com>

* regenerate yarn.lock

* revert translation changes

Co-authored-by: Bruno Crosier <bruno.crosier@gmail.com>
Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
2022-04-14 15:23:31 -04:00
Christine Legge
0a4bcb62da add 1bp fee tier to polygon (#3724) 2022-04-14 15:11:58 -04:00
Zach Pomerantz
f50bcbdb2d fix: initial transitions (#3719)
* fix: rm action fade

* fix: disallow stale swaps

* fix: fade in buttons

* fix: fade in input text

* fix: standardize border handling

* fix: transition token button width

* fix: cleanup transitions

* fix: use transition for button

* chore: cleanup
2022-04-13 11:45:29 -07:00
Christine Legge
cbe421ee23 fix: Reload the app when there is a javascript error and a new version of the app (#3715)
* reload the app when encountering a javascript error if there is an update

* remove console.logs

* Add more comments
2022-04-13 13:53:54 -04:00
Zach Pomerantz
3439786c38 feat: display connecting state (#3713) 2022-04-13 09:21:28 -07:00
Zach Pomerantz
6294915be6 fix: convert token list to context (#3712)
* fix: convert token list to context

* fix: cosmos
2022-04-12 14:53:50 -07:00
Zach Pomerantz
984c742d0e fix: use context for block number (#3708)
* fix: use context for block number

* fix: check for valid BlockNumberContext
2022-04-12 10:10:57 -07:00
Zach Pomerantz
00b151d7fa fix: activation frames (#3711) 2022-04-12 09:23:19 -07:00
guil-lambert
5967cf5d9d fix: bug where user cannot burn lp position if fetching fee values fails. (#3633)
* fix: can burn position even if fetching fees fails.

* Revert "fix: can burn position even if fetching fees fails."

This reverts commit a96f7178e5.

* recover more gracefully from failed fee fetch

Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
2022-04-12 11:41:31 -04:00
Zach Pomerantz
e480f0ebe5 chore: simplify swap info (#3710)
* fix: prevent unnecessary TokenImg renders

* fix: prevent unnecessary trade renders

* fix: simplify swap info computation
2022-04-11 17:09:11 -07:00
Zach Pomerantz
f6ceecbc5e fix: update hook deps to improve ref equality checks (#3707)
* fix: prevent unnecessary TokenImg renders

* fix: prevent unnecessary trade renders
2022-04-11 16:57:53 -07:00
Zach Pomerantz
a0348b45be chore: bump to v1.0.6 (#3696) 2022-04-08 13:13:11 -07:00
Zach Pomerantz
e4b37cffcc fix: skewed swap info state (#3695)
* fix: skewed swap info state

* fix: typings
2022-04-08 13:11:19 -07:00
Zach Pomerantz
dd69cccf91 fix: always run global updaters (#3694) 2022-04-08 12:38:39 -07:00
Zach Pomerantz
8b228de88f chore: bump to v1.0.5 (#3691) 2022-04-08 10:52:30 -07:00
Zach Pomerantz
f91fc3c6a6 fix: defer layout effects (#3687)
* fix: use effect for color

* chore: clean up token defaults

* fix: condition updaters on active tokens
2022-04-08 10:27:10 -07:00
Zach Pomerantz
e0e2b40f9f chore: bump to v1.0.4 (#3686) 2022-04-07 15:05:35 -07:00
Zach Pomerantz
bc1c61b63a fix: omit document ref (#3685) 2022-04-07 15:05:11 -07:00
Alex Dorsch
446ad3e0d4 fix: missing token balance (#3661)
* increase gas required to read token balance

* set token balance gas requirement to 185_000
2022-04-07 15:00:03 -07:00
Zach Pomerantz
65e58a08cf fix: show i18n keys while messages load (#3683)
* fix: show i18n keys while messages load

* fix: i18n initialization check
2022-04-07 14:55:09 -07:00
Zach Pomerantz
71b20b432c fix: block number stability (#3684)
* fix: block number stability

* fix: chainBlock logic
2022-04-07 14:26:50 -07:00
Zach Pomerantz
ecfa179b3f chore: bump to v1.0.3 2022-04-07 11:24:33 -07:00
Zach Pomerantz
6c94a0f585 fix: swap validator (#3682) 2022-04-07 11:23:21 -07:00
Zach Pomerantz
600aeaaff1 fix: polling memory leak (#3676)
* chore: clarify stale callback

* fix: polling memory leak
2022-04-06 17:21:44 -07:00
Zach Pomerantz
3bfbc74e47 chore: bump to v1.0.2 (#3675) 2022-04-06 13:05:06 -07:00
Zach Pomerantz
84f76e34b2 fix: do not fetch wrap price (#3673)
* fix: do not fetch wrap price

* fix: abort trade computation for wraps
2022-04-06 13:04:16 -07:00
Zach Pomerantz
b965bed865 fix: token input height (#3672) 2022-04-06 12:14:15 -07:00
Zach Pomerantz
a9039e8d0b chore: bump to v1.0.1 (#3670) 2022-04-06 10:02:37 -07:00
Zach Pomerantz
60d35b46f3 fix: simplify validation (#3665)
* fix: simplify widget validation

* test: update cosmos to trigger edge cases

* fix: simplify swap validation
2022-04-06 09:21:50 -07:00
Ian Lapham
3d422cf707 update address list (#3669) 2022-04-06 11:48:47 -04:00
Zach Pomerantz
84c70ac84d chore: bump to v1.0.0 (#3663) 2022-04-05 10:45:36 -07:00
Zach Pomerantz
de3a33dfcb fix: stale data edge cases (#3657)
* fix: stale chain block

* chore: simplify atom usage

* fix: support single-token chain

* fix: avoid extra rpcs

* chore: rename isDisabled

* fix: simplify useUSDCPrice

* fix: simplify useComputeSwapInfo

* chore: include type

* fix: guard hasAmounts
2022-04-05 10:45:21 -07:00
Zach Pomerantz
99a084f230 fix: JsonRpc url wrapper (#3662)
* fix: JsonRpc url wrapper

* chore: finish renaming
2022-04-05 10:00:17 -07:00
Jordan Frankfurt
e880955743 chore(widgets): bump version (#3645) 2022-04-01 18:06:12 -04:00
Zach Pomerantz
bbf43fcd27 fix: walletconnect numeric chain id (#3643) 2022-04-01 10:57:11 -07:00
Zach Pomerantz
a00ac56389 chore: bump to v0.0.30-beta (#3637) 2022-03-31 14:49:44 -07:00
Noah Zinsmeister
7201944bc2 Revert "fix(error handling): try reloading the app when encountering a javascript error (#3435)"
This reverts commit 5cf9e84db5.
2022-03-31 17:24:47 -04:00
Noah Zinsmeister
b0ff0f83b0 fix crash (#3634) 2022-03-31 16:41:01 -04:00
Moody Salem
5cf9e84db5 fix(error handling): try reloading the app when encountering a javascript error (#3435) 2022-03-31 16:39:31 -04:00
Zach Pomerantz
c0bdb8db12 fix: break unnecessary hierarchical deps (#3629) 2022-03-30 21:23:33 -07:00
Zach Pomerantz
2d8f767d74 feat: upgrade web3-react (#3628)
* chore: upgrade web3-react

* feat: use a JsonRpcConnector

* chore: rm @ethersproject/experimental

* fix: assert Web3Provider in app

* fix: type providers more loosely

* chore: reinstall experimental for testing
2022-03-30 20:45:43 -07:00
Zach Pomerantz
1303416eca feat: lazy load the lib's smart-order-router (#3624)
* feat: lazy-load the smart-order-router

* chore: guard against regression
2022-03-30 14:09:18 -07:00
Zach Pomerantz
124f6420a5 fix: lazy load en-us (#3626) 2022-03-30 13:32:49 -07:00
Zach Pomerantz
91f5fc0881 chore: upgrade redux-multicall and smart-order-router (#3623)
* chore: upgrade redux-multicall and smart-order-router

* chore: provide @ethersproject through ethers
2022-03-29 12:00:56 -07:00
Zach Pomerantz
ec831f8433 chore: destructure json imports (#3622) 2022-03-29 12:00:42 -07:00
Crowdin Bot
56bd9b68d7 chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-29 06:07:44 +00:00
Crowdin Bot
865d21f039 chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-28 23:07:52 +00:00
Jordan Frankfurt
dceadf8472 chore(docs): add widgets info to the repo README (#3600)
* add widgets info to the repo README

* pr feedback from z

* pr feedback from w and z

* separate README.md and INTERFACE_README.md

* Update WIDGETS_README.md

Co-authored-by: Will Hennessy <hennessywill@gmail.com>

Co-authored-by: Will Hennessy <hennessywill@gmail.com>
2022-03-28 11:35:14 -05:00
Crowdin Bot
cd3a91bca8 chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-25 07:06:55 +00:00
Connor McEwen
de1f5d1adc feat: migrate to GA4 (#3599) 2022-03-24 21:44:48 -04:00
Crowdin Bot
b5d403768f chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-24 20:07:03 +00:00
Zach Pomerantz
c4c811aeb3 chore: bump to v0.0.26-beta (#3593) 2022-03-24 16:02:38 -04:00
Zach Pomerantz
33c24a3f05 fix: trade displays (#3594)
* fix: show syncing over insufficient balance

* fix: mv anti-janking up a level

* feat: add error caption for completeness

* chore: clarify naming
2022-03-24 16:02:20 -04:00
Zach Pomerantz
9ef2b3a116 chore: add ethers (#3591) 2022-03-24 12:51:03 -04:00
Zach Pomerantz
afe38a2d10 fix: tick before returning quote (#3598) 2022-03-24 12:33:35 -04:00
dependabot[bot]
d28607a1c8 chore(deps-dev): bump @uniswap/default-token-list from 3.0.0 to 3.1.0 (#3564) 2022-03-24 12:09:44 -04:00
dependabot[bot]
7fb363ac46 chore(deps): bump @uniswap/token-lists (#3596) 2022-03-24 12:09:22 -04:00
133 changed files with 1554 additions and 1862 deletions

View File

@@ -1,5 +1,4 @@
REACT_APP_INFURA_KEY="099fc58e0de9451d80b18d7c74caa7c1"
REACT_APP_PORTIS_ID="c0e2bf01-4b08-4fd5-ac7b-8e26b58cd236"
REACT_APP_FORTMATIC_KEY="pk_live_F937DF033A1666BF"
REACT_APP_GOOGLE_ANALYTICS_ID="UA-128182339-4"
REACT_APP_GOOGLE_ANALYTICS_ID="G-KDP9B6W4H8"
REACT_APP_FIREBASE_KEY="AIzaSyBcZWwTcTJHj_R6ipZcrJkXdq05PuX0Rs0"

45
INTERFACE_README.md Normal file
View File

@@ -0,0 +1,45 @@
# 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).

View File

@@ -1,51 +1,21 @@
# Uniswap Interface
This repo is home to the Uniswap Widgets package and the web app interface [app.uniswap.org](https://app.uniswap.org).
[![Unit Tests](https://github.com/Uniswap/uniswap-interface/actions/workflows/unit-tests.yaml/badge.svg)](https://github.com/Uniswap/uniswap-interface/actions/workflows/unit-tests.yaml)
[![Integration Tests](https://github.com/Uniswap/uniswap-interface/actions/workflows/integration-tests.yaml/badge.svg)](https://github.com/Uniswap/uniswap-interface/actions/workflows/integration-tests.yaml)
[![Lint](https://github.com/Uniswap/uniswap-interface/actions/workflows/lint.yml/badge.svg)](https://github.com/Uniswap/uniswap-interface/actions/workflows/lint.yml)
[![Release](https://github.com/Uniswap/uniswap-interface/actions/workflows/release.yaml/badge.svg)](https://github.com/Uniswap/uniswap-interface/actions/workflows/release.yaml)
# Uniswap Labs Interface
[![Unit Tests](https://github.com/Uniswap/interface/actions/workflows/unit-tests.yaml/badge.svg)](https://github.com/Uniswap/interface/actions/workflows/unit-tests.yaml)
[![Integration Tests](https://github.com/Uniswap/interface/actions/workflows/integration-tests.yaml/badge.svg)](https://github.com/Uniswap/interface/actions/workflows/integration-tests.yaml)
[![Lint](https://github.com/Uniswap/interface/actions/workflows/lint.yml/badge.svg)](https://github.com/Uniswap/interface/actions/workflows/lint.yml)
[![Release](https://github.com/Uniswap/interface/actions/workflows/release.yaml/badge.svg)](https://github.com/Uniswap/interface/actions/workflows/release.yaml)
[![Crowdin](https://badges.crowdin.net/uniswap-interface/localized.svg)](https://crowdin.com/project/uniswap-interface)
An open source interface for Uniswap -- a protocol for decentralized exchange of Ethereum tokens.
The web application hosted at https://app.uniswap.org is a convenient way to access the core functionality of the Uniswap Protocol.
- 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)
For documentation of the interface including how to contribute or access prior builds, please view the README here: [INTERFACE_README.md](./INTERFACE_README.md)
## Accessing the Uniswap Interface
# Uniswap Labs Widgets
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 `@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.
## Unsupported tokens
The npm package can be found here. [@uniswap/widgets](https://www.npmjs.com/package/@uniswap/widgets)
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).
For documentation of the widgets package, please view the README here: [WIDGETS_README.md](./WIDGETS_README.md).

40
WIDGETS_README.md Normal file
View File

@@ -0,0 +1,40 @@
# 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.
![swap widget screenshot](https://raw.githubusercontent.com/Uniswap/interface/main/src/assets/images/widget-screenshot.png)
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.

View File

@@ -1,6 +1,6 @@
{
"name": "@uniswap/widgets",
"version": "0.0.25-beta",
"version": "1.0.6",
"description": "Uniswap Interface",
"homepage": ".",
"files": [
@@ -30,6 +30,7 @@
"@babel/preset-env": "^7.16.11",
"@babel/preset-react": "^7.16.7",
"@babel/preset-typescript": "^7.16.7",
"@ethersproject/experimental": "^5.4.0",
"@gnosis.pm/safe-apps-web3-react": "^0.6.0",
"@graphql-codegen/cli": "1.21.5",
"@graphql-codegen/typescript": "1.22.3",
@@ -89,8 +90,8 @@
"@uniswap/v2-periphery": "^1.1.0-beta.0",
"@uniswap/v3-core": "1.0.0",
"@uniswap/v3-periphery": "^1.1.1",
"@web3-react/metamask": "8.0.13-beta.0",
"@web3-react/walletconnect": "8.0.18-beta.0",
"@web3-react/metamask": "^8.0.19-beta.0",
"@web3-react/walletconnect": "^8.0.26-beta.0",
"array.prototype.flat": "^1.2.4",
"array.prototype.flatmap": "^1.2.4",
"babel-plugin-macros": "^3.1.0",
@@ -118,7 +119,7 @@
"react-confetti": "^6.0.0",
"react-cosmos": "^5.6.6",
"react-dom": "^17.0.1",
"react-ga": "^2.5.7",
"react-ga4": "^1.4.1",
"react-is": "^17.0.2",
"react-markdown": "^4.3.1",
"react-redux": "^7.2.2",
@@ -149,7 +150,6 @@
"web3-react-abstract-connector": "npm:@web3-react/abstract-connector@^6.0.7",
"web3-react-fortmatic-connector": "npm:@web3-react/fortmatic-connector@^6.0.9",
"web3-react-injected-connector": "npm:@web3-react/injected-connector@^6.0.7",
"web3-react-portis-connector": "npm:@web3-react/portis-connector@^6.0.9",
"web3-react-types": "npm:@web3-react/types@^6.0.7",
"web3-react-walletconnect-connector": "npm:@web3-react/walletconnect-connector@^7.0.2-alpha.0",
"web3-react-walletlink-connector": "npm:@web3-react/walletlink-connector@^6.2.13",
@@ -193,39 +193,25 @@
"license": "GPL-3.0-or-later",
"dependencies": {
"@babel/runtime": "^7.17.0",
"@ethersproject/abi": "^5.4.1",
"@ethersproject/abstract-provider": "^5.4.1",
"@ethersproject/abstract-signer": "^5.4.1",
"@ethersproject/address": "^5.4.0",
"@ethersproject/bignumber": "^5.4.2",
"@ethersproject/bytes": "^5.4.0",
"@ethersproject/constants": "^5.4.0",
"@ethersproject/contracts": "^5.4.1",
"@ethersproject/experimental": "^5.4.0",
"@ethersproject/hash": "^5.4.0",
"@ethersproject/providers": "^5.4.0",
"@ethersproject/solidity": "^5.4.0",
"@ethersproject/strings": "^5.4.0",
"@ethersproject/units": "^5.4.0",
"@ethersproject/wallet": "^5.4.0",
"@fontsource/ibm-plex-mono": "^4.5.1",
"@fontsource/inter": "^4.5.1",
"@popperjs/core": "^2.4.4",
"@reduxjs/toolkit": "^1.6.1",
"@uniswap/redux-multicall": "^1.0.0",
"@uniswap/redux-multicall": "^1.1.1",
"@uniswap/router-sdk": "^1.0.3",
"@uniswap/sdk-core": "^3.0.1",
"@uniswap/smart-order-router": "^2.5.20",
"@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.17-beta.0",
"@web3-react/eip1193": "8.0.15-beta.0",
"@web3-react/empty": "8.0.10-beta.0",
"@web3-react/types": "8.0.10-beta.0",
"@web3-react/url": "8.0.12-beta.0",
"@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",

View File

@@ -37,6 +37,11 @@ function isAsset(source: string) {
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
@@ -60,7 +65,7 @@ const plugins = [
const check = {
input: 'src/lib/index.tsx',
output: { file: 'dist/widgets.tsc', inlineDynamicImports: true },
external: isAsset,
external: (source: string) => isAsset(source) || isEthers(source),
plugins: [
externals({ exclude: ['constants'], deps: true, peerDeps: true }), // marks builtins, dependencies, and peerDependencies external
...plugins,
@@ -72,7 +77,7 @@ const check = {
const type = {
input: 'dist/dts/lib/index.d.ts',
output: { file: 'dist/index.d.ts' },
external: isAsset,
external: (source: string) => isAsset(source) || isEthers(source),
plugins: [
externals({ exclude: ['constants'], deps: true, peerDeps: true }),
dts({ compilerOptions: { baseUrl: 'dist/dts' } }),
@@ -112,6 +117,7 @@ const transpile = {
sourcemap: false,
},
],
external: isEthers,
plugins: [
externals({
exclude: [

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

View File

@@ -8,7 +8,7 @@ import styled, { ThemeContext } from 'styled-components/macro'
import { AbstractConnector } from 'web3-react-abstract-connector'
import { ReactComponent as Close } from '../../assets/images/x.svg'
import { injected, portis, walletlink } from '../../connectors'
import { injected, walletlink } from '../../connectors'
import { SUPPORTED_WALLETS } from '../../constants/wallet'
import { clearAllTransactions } from '../../state/transactions/actions'
import { ExternalLink, LinkStyledButton, ThemedText } from '../../theme'
@@ -181,15 +181,6 @@ function WrappedStatusIcon({ connector }: { connector: AbstractConnector | Conne
return (
<IconWrapper size={16}>
<StatusIcon connector={connector} />
{connector === portis && (
<MainWalletAction
onClick={() => {
portis.portis.showPortis()
}}
>
<Trans>Show Portis</Trans>
</MainWalletAction>
)}
</IconWrapper>
)
}
@@ -210,10 +201,6 @@ const WalletAction = styled(ButtonSecondary)`
}
`
const MainWalletAction = styled(WalletAction)`
color: ${({ theme }) => theme.primary1};
`
function renderTransactions(transactions: string[]) {
return (
<TransactionListWrapper>

View File

@@ -33,6 +33,7 @@ const BLOCKED_ADDRESSES: string[] = [
'0x5512d943ed1f7c8a43f3435c85f7ab68b30121b0',
'0xc455f7fd3e0e12afd51fba5c106909934d8a0e4a',
'0x629e7Da20197a5429d30da36E77d06CdF796b71A',
'0x7FF9cFad3877F21d41Da833E2F775dB0569eE3D9',
]
export default function Blocklist({ children }: { children: ReactNode }) {

View File

@@ -1,6 +1,6 @@
import { Trans } from '@lingui/macro'
import React, { ErrorInfo } from 'react'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import styled from 'styled-components/macro'
import store, { AppState } from '../../state'
@@ -49,6 +49,13 @@ type ErrorBoundaryState = {
const IS_UNISWAP = window.location.hostname === 'app.uniswap.org'
async function updateServiceWorker(): Promise<ServiceWorkerRegistration> {
const ready = await navigator.serviceWorker.ready
// the return type of update is incorrectly typed as Promise<void>. See
// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/update
return ready.update() as unknown as Promise<ServiceWorkerRegistration>
}
export default class ErrorBoundary extends React.Component<unknown, ErrorBoundaryState> {
constructor(props: unknown) {
super(props)
@@ -56,15 +63,29 @@ export default class ErrorBoundary extends React.Component<unknown, ErrorBoundar
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
updateServiceWorker()
.then(async (registration) => {
// We want to refresh only if we detect a new service worker is waiting to be activated.
// See details about it: https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle
if (registration?.waiting) {
await registration.unregister()
// Makes Workbox call skipWaiting(). For more info on skipWaiting see: https://developer.chrome.com/docs/workbox/handling-service-worker-updates/
registration.waiting.postMessage({ type: 'SKIP_WAITING' })
// Once the service worker is unregistered, we can reload the page to let
// the browser download a fresh copy of our app (invalidating the cache)
window.location.reload()
}
})
.catch((error) => {
console.error('Failed to update service worker', error)
})
return { error }
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
ReactGA.exception({
...error,
...errorInfo,
fatal: true,
})
ReactGA.event('exception', { description: error.toString() + errorInfo.toString(), fatal: true })
}
render() {

View File

@@ -11,7 +11,7 @@ import { PoolState, usePools } from 'hooks/usePools'
import usePrevious from 'hooks/usePrevious'
import { DynamicSection } from 'pages/AddLiquidity/styled'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import { Box } from 'rebass'
import styled, { keyframes } from 'styled-components/macro'
import { ThemedText } from 'theme'

View File

@@ -10,7 +10,7 @@ export const FEE_AMOUNT_DETAIL: Record<
[FeeAmount.LOWEST]: {
label: '0.01',
description: <Trans>Best for very stable pairs.</Trans>,
supportedChains: [SupportedChainId.MAINNET],
supportedChains: [SupportedChainId.MAINNET, SupportedChainId.POLYGON, SupportedChainId.POLYGON_MUMBAI],
},
[FeeAmount.LOW]: {
label: '0.05',

View File

@@ -255,8 +255,8 @@ export default function NetworkSelector() {
const handleChainSwitch = useCallback(
(targetChain: number, skipToggle?: boolean) => {
if (!library) return
switchToNetwork({ library, chainId: targetChain })
if (!library?.provider) return
switchToNetwork({ provider: library.provider, chainId: targetChain })
.then(() => {
if (!skipToggle) {
toggle()

View File

@@ -3,9 +3,8 @@ import { AbstractConnector } from 'web3-react-abstract-connector'
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
import FortmaticIcon from '../../assets/images/fortmaticIcon.png'
import PortisIcon from '../../assets/images/portisIcon.png'
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
import { fortmatic, injected, portis, walletconnect, walletlink } from '../../connectors'
import { fortmatic, injected, walletconnect, walletlink } from '../../connectors'
import Identicon from '../Identicon'
export default function StatusIcon({ connector }: { connector: AbstractConnector | Connector }) {
@@ -18,8 +17,6 @@ export default function StatusIcon({ connector }: { connector: AbstractConnector
return <img src={CoinbaseWalletIcon} alt={'Coinbase Wallet'} />
case fortmatic:
return <img src={FortmaticIcon} alt={'Fortmatic'} />
case portis:
return <img src={PortisIcon} alt={'Portis'} />
default:
return null
}

View File

@@ -9,7 +9,7 @@ import useTheme from 'hooks/useTheme'
import { saturate } from 'polished'
import React, { ReactNode, useCallback, useMemo } from 'react'
import { BarChart2, CloudOff, Inbox } from 'react-feather'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import { batch } from 'react-redux'
import { Bound } from 'state/mint/v3/actions'
import styled from 'styled-components/macro'
@@ -158,11 +158,7 @@ export default function LiquidityChartRangeInput({
)
if (isError) {
ReactGA.exception({
...error,
category: 'Liquidity',
fatal: false,
})
ReactGA.event('exception', { description: error.toString(), fatal: false })
}
return (

View File

@@ -3,7 +3,7 @@ import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useCallback, useEffect } from 'react'
import { Heart, X } from 'react-feather'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import styled, { keyframes } from 'styled-components/macro'
import tokenLogo from '../../assets/images/token-logo.png'

View File

@@ -4,7 +4,7 @@ import { RowFixed } from 'components/Row'
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
import { useEffect } from 'react'
import { MessageCircle, X } from 'react-feather'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import { useShowSurveyPopup } from 'state/user/hooks'
import styled from 'styled-components/macro'
import { ExternalLink, ThemedText, Z_INDEX } from 'theme'

View File

@@ -3,7 +3,7 @@ import Card, { DarkGreyCard } from 'components/Card'
import Row, { AutoRow, RowBetween } from 'components/Row'
import { useEffect, useRef } from 'react'
import { ArrowDown, Info, X } from 'react-feather'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import styled from 'styled-components/macro'
import { ExternalLink, ThemedText } from 'theme'
import { isMobile } from 'utils/userAgent'

View File

@@ -2,7 +2,7 @@ import { Trans } from '@lingui/macro'
import { ButtonOutlined } from 'components/Button'
import { AutoRow } from 'components/Row'
import React from 'react'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'

View File

@@ -11,7 +11,7 @@ import { getTokenFilter } from 'lib/hooks/useTokenList/filtering'
import { tokenComparator, useSortTokensByQuery } from 'lib/hooks/useTokenList/sorting'
import { KeyboardEvent, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Edit } from 'react-feather'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import AutoSizer from 'react-virtualized-auto-sizer'
import { FixedSizeList } from 'react-window'
import { Text } from 'rebass'

View File

@@ -11,7 +11,7 @@ import useTheme from 'hooks/useTheme'
import { transparentize } from 'polished'
import { useCallback, useState } from 'react'
import { AlertTriangle, ArrowLeft } from 'react-feather'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import { useAppDispatch } from 'state/hooks'
import { enableList, removeList } from 'state/lists/actions'
import { useAllLists } from 'state/lists/hooks'

View File

@@ -9,7 +9,7 @@ import parseENSAddress from 'lib/utils/parseENSAddress'
import uriToHttp from 'lib/utils/uriToHttp'
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { CheckCircle, Settings } from 'react-feather'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import { usePopper } from 'react-popper'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import styled from 'styled-components/macro'

View File

@@ -5,7 +5,7 @@ import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { AUTO_ROUTER_SUPPORTED_CHAINS } from 'lib/hooks/routing/clientSideSmartOrderRouter'
import { useContext, useRef, useState } from 'react'
import { Settings, X } from 'react-feather'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import { Text } from 'rebass'
import styled, { ThemeContext } from 'styled-components/macro'

View File

@@ -5,7 +5,7 @@ import Row, { AutoRow, RowBetween } from 'components/Row'
import { useWalletConnectMonitoringEventCallback } from 'hooks/useMonitoringEventCallback'
import { useEffect, useState } from 'react'
import { ArrowLeft, ArrowRight, Info } from 'react-feather'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import styled from 'styled-components/macro'
import { AbstractConnector } from 'web3-react-abstract-connector'
import { UnsupportedChainIdError, useWeb3React } from 'web3-react-core'
@@ -13,7 +13,7 @@ import { WalletConnectConnector } from 'web3-react-walletconnect-connector'
import MetamaskIcon from '../../assets/images/metamask.png'
import { ReactComponent as Close } from '../../assets/images/x.svg'
import { fortmatic, injected, portis } from '../../connectors'
import { fortmatic, injected } from '../../connectors'
import { OVERLAY_READY } from '../../connectors/Fortmatic'
import { SUPPORTED_WALLETS } from '../../constants/wallet'
import usePrevious from '../../hooks/usePrevious'
@@ -228,11 +228,6 @@ export default function WalletModal({
const option = SUPPORTED_WALLETS[key]
// check for mobile options
if (isMobile) {
//disable portis on mobile for now
if (option.connector === portis) {
return null
}
if (!window.web3 && !window.ethereum && option.mobile) {
return (
<Option

View File

@@ -1,18 +1,13 @@
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useEffect } from 'react'
import ReactGA from 'react-ga'
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.timing({
category: 'Web Vitals',
variable: name,
value: Math.round(name === 'CLS' ? delta * 1000 : delta),
label: id,
})
ReactGA._gaCommandSendTiming('Web Vitals', name, Math.round(name === 'CLS' ? delta * 1000 : delta), id)
}
// tracks web vitals and pageviews
@@ -35,7 +30,7 @@ export default function GoogleAnalyticsReporter({ location: { pathname, search }
}, [pathname, search])
useEffect(() => {
// typed as 'any' in react-ga -.-
// typed as 'any' in react-ga4 -.-
ReactGA.ga((tracker: any) => {
if (!tracker) return

View File

@@ -1,4 +1,4 @@
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import { isMobile } from 'utils/userAgent'
export const GOOGLE_ANALYTICS_CLIENT_ID_STORAGE_KEY = 'ga_client_id'
@@ -23,5 +23,5 @@ if (typeof GOOGLE_ANALYTICS_ID === 'string') {
: 'mobileRegular',
})
} else {
ReactGA.initialize('test', { testMode: true, debug: true })
ReactGA.initialize('test', { gtagOptions: { debug_mode: true } })
}

View File

@@ -4,7 +4,7 @@ import { AutoColumn } from 'components/Column'
import { LoadingOpacityContainer } from 'components/Loader/styled'
import { RowFixed } from 'components/Row'
import { MouseoverTooltipContent } from 'components/Tooltip'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import { InterfaceTrade } from 'state/routing/types'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'

View File

@@ -3,7 +3,6 @@ import { SafeAppConnector } from '@gnosis.pm/safe-apps-web3-react'
import { ALL_SUPPORTED_CHAIN_IDS, SupportedChainId } from 'constants/chains'
import { INFURA_NETWORK_URLS } from 'constants/infura'
import { InjectedConnector } from 'web3-react-injected-connector'
import { PortisConnector } from 'web3-react-portis-connector'
import { WalletConnectConnector } from 'web3-react-walletconnect-connector'
import { WalletLinkConnector } from 'web3-react-walletlink-connector'
@@ -13,7 +12,6 @@ import { FortmaticConnector } from './Fortmatic'
import { NetworkConnector } from './NetworkConnector'
const FORMATIC_KEY = process.env.REACT_APP_FORTMATIC_KEY
const PORTIS_ID = process.env.REACT_APP_PORTIS_ID
export const network = new NetworkConnector({
urls: INFURA_NETWORK_URLS,
@@ -43,12 +41,6 @@ export const fortmatic = new FortmaticConnector({
chainId: 1,
})
// mainnet only
export const portis = new PortisConnector({
dAppId: PORTIS_ID ?? '',
networks: [1],
})
export const walletlink = new WalletLinkConnector({
url: INFURA_NETWORK_URLS[SupportedChainId.MAINNET],
appName: 'Uniswap',

View File

@@ -35,10 +35,7 @@ export const SUPPORTED_LOCALES = [
]
export type SupportedLocale = typeof SUPPORTED_LOCALES[number] | 'pseudo'
// eslint-disable-next-line import/first
import * as enUS from 'locales/en-US'
export const DEFAULT_LOCALE: SupportedLocale = 'en-US'
export const DEFAULT_CATALOG = enUS
export const LOCALE_LABEL: { [locale in SupportedLocale]: string } = {
'af-ZA': 'Afrikaans',

View File

@@ -1,23 +1,86 @@
import { Currency, Ether, NativeCurrency, Token, WETH9 } from '@uniswap/sdk-core'
import {
USDC_ARBITRUM,
USDC_ARBITRUM_RINKEBY,
USDC_GÖRLI,
USDC_KOVAN,
USDC_MAINNET,
USDC_OPTIMISM,
USDC_OPTIMISTIC_KOVAN,
USDC_POLYGON,
USDC_POLYGON_MUMBAI,
USDC_RINKEBY,
USDC_ROPSTEN,
} from '@uniswap/smart-order-router'
import invariant from 'tiny-invariant'
import { UNI_ADDRESS } from './addresses'
import { SupportedChainId } from './chains'
export { USDC_ARBITRUM, USDC_MAINNET, USDC_OPTIMISM, USDC_POLYGON }
export const USDC_MAINNET = new Token(
SupportedChainId.MAINNET,
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
6,
'USDC',
'USD//C'
)
export const USDC_ROPSTEN = new Token(
SupportedChainId.ROPSTEN,
'0x07865c6e87b9f70255377e024ace6630c1eaa37f',
6,
'USDC',
'USD//C'
)
export const USDC_RINKEBY = new Token(
SupportedChainId.RINKEBY,
'0x4DBCdF9B62e891a7cec5A2568C3F4FAF9E8Abe2b',
6,
'tUSDC',
'test USD//C'
)
export const USDC_GOERLI = new Token(
SupportedChainId.GOERLI,
'0x07865c6e87b9f70255377e024ace6630c1eaa37f',
6,
'USDC',
'USD//C'
)
export const USDC_KOVAN = new Token(
SupportedChainId.KOVAN,
'0x31eeb2d0f9b6fd8642914ab10f4dd473677d80df',
6,
'USDC',
'USD//C'
)
export const USDC_OPTIMISM = new Token(
SupportedChainId.OPTIMISM,
'0x7F5c764cBc14f9669B88837ca1490cCa17c31607',
6,
'USDC',
'USD//C'
)
export const USDC_OPTIMISTIC_KOVAN = new Token(
SupportedChainId.OPTIMISTIC_KOVAN,
'0x3b8e53b3ab8e01fb57d0c9e893bc4d655aa67d84',
6,
'USDC',
'USD//C'
)
export const USDC_ARBITRUM = new Token(
SupportedChainId.ARBITRUM_ONE,
'0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8',
6,
'USDC',
'USD//C'
)
export const USDC_ARBITRUM_RINKEBY = new Token(
SupportedChainId.ARBITRUM_RINKEBY,
'0x09b98f8b2395d076514037ff7d39a091a536206c',
6,
'USDC',
'USD//C'
)
export const USDC_POLYGON = new Token(
SupportedChainId.POLYGON,
'0x2791bca1f2de4661ed88a30c99a7a9449aa84174',
6,
'USDC',
'USD//C'
)
export const USDC_POLYGON_MUMBAI = new Token(
SupportedChainId.POLYGON_MUMBAI,
'0xe11a86849d99f524cac3e7a0ec1241828e332c62',
6,
'USDC',
'USD//C'
)
export const AMPL = new Token(
SupportedChainId.MAINNET,
@@ -55,7 +118,7 @@ export const USDC: { [chainId in SupportedChainId]: Token } = {
[SupportedChainId.OPTIMISTIC_KOVAN]: USDC_OPTIMISTIC_KOVAN,
[SupportedChainId.POLYGON]: USDC_POLYGON,
[SupportedChainId.POLYGON_MUMBAI]: USDC_POLYGON_MUMBAI,
[SupportedChainId.GOERLI]: USDC_GÖRLI,
[SupportedChainId.GOERLI]: USDC_GOERLI,
[SupportedChainId.RINKEBY]: USDC_RINKEBY,
[SupportedChainId.KOVAN]: USDC_KOVAN,
[SupportedChainId.ROPSTEN]: USDC_ROPSTEN,
@@ -310,7 +373,7 @@ export const TOKEN_SHORTHANDS: { [shorthand: string]: { [chainId in SupportedCha
[SupportedChainId.OPTIMISTIC_KOVAN]: USDC_OPTIMISTIC_KOVAN.address,
[SupportedChainId.POLYGON]: USDC_POLYGON.address,
[SupportedChainId.POLYGON_MUMBAI]: USDC_POLYGON_MUMBAI.address,
[SupportedChainId.GOERLI]: USDC_GÖRLI.address,
[SupportedChainId.GOERLI]: USDC_GOERLI.address,
[SupportedChainId.RINKEBY]: USDC_RINKEBY.address,
[SupportedChainId.KOVAN]: USDC_KOVAN.address,
[SupportedChainId.ROPSTEN]: USDC_ROPSTEN.address,

View File

@@ -4,9 +4,8 @@ import INJECTED_ICON_URL from '../assets/images/arrow-right.svg'
import COINBASE_ICON_URL from '../assets/images/coinbaseWalletIcon.svg'
import FORTMATIC_ICON_URL from '../assets/images/fortmaticIcon.png'
import METAMASK_ICON_URL from '../assets/images/metamask.png'
import PORTIS_ICON_URL from '../assets/images/portisIcon.png'
import WALLETCONNECT_ICON_URL from '../assets/images/walletConnectIcon.svg'
import { fortmatic, injected, portis, walletconnect, walletlink } from '../connectors'
import { fortmatic, injected, walletconnect, walletlink } from '../connectors'
interface WalletInfo {
connector?: AbstractConnector
@@ -73,13 +72,4 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
color: '#6748FF',
mobile: true,
},
Portis: {
connector: portis,
name: 'Portis',
iconURL: PORTIS_ICON_URL,
description: 'Login using Portis hosted wallet',
href: null,
color: '#4A6C9B',
mobile: true,
},
}

View File

@@ -15,7 +15,7 @@ export default function useAddTokenToMetamask(currencyToAdd: Currency | undefine
const logoURL = useCurrencyLogoURIs(token)[0]
const addToken = useCallback(() => {
if (library && library.provider.isMetaMask && library.provider.request && token) {
if (library && library?.provider?.isMetaMask && library.provider.request && token) {
library.provider
.request({
method: 'wallet_watchAsset',

View File

@@ -13,6 +13,7 @@ import useUSDCPrice, { useUSDCValue } from './useUSDCPrice'
const V3_SWAP_DEFAULT_SLIPPAGE = new Percent(50, 10_000) // .50%
const ONE_TENTHS_PERCENT = new Percent(10, 10_000) // .10%
export const DEFAULT_AUTO_SLIPPAGE = ONE_TENTHS_PERCENT
/**
* Return a guess of the gas cost used in computing slippage tolerance for a given trade
@@ -41,10 +42,10 @@ export default function useAutoSlippageTolerance(
const gasEstimate = guesstimateGas(trade)
const nativeCurrency = useNativeCurrency()
const nativeCurrencyPrice = useUSDCPrice(nativeCurrency ?? undefined)
const nativeCurrencyPrice = useUSDCPrice((trade && nativeCurrency) ?? undefined)
return useMemo(() => {
if (!trade || onL2) return ONE_TENTHS_PERCENT
if (!trade || onL2) return DEFAULT_AUTO_SLIPPAGE
const nativeGasCost =
nativeGasPrice && typeof gasEstimate === 'number'

View File

@@ -3,7 +3,7 @@ import { SupportedChainId } from 'constants/chains'
import uriToHttp from 'lib/utils/uriToHttp'
import Vibrant from 'node-vibrant/lib/bundle.js'
import { shade } from 'polished'
import { useLayoutEffect, useState } from 'react'
import { useEffect, useState } from 'react'
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
import { hex } from 'wcag-contrast'
@@ -64,7 +64,7 @@ async function getColorFromUriPath(uri: string): Promise<string | null> {
export function useColor(token?: Token) {
const [color, setColor] = useState('#2172E5')
useLayoutEffect(() => {
useEffect(() => {
let stale = false
if (token) {
@@ -87,7 +87,7 @@ export function useColor(token?: Token) {
export function useListColor(listImageUri?: string) {
const [color, setColor] = useState('#2172E5')
useLayoutEffect(() => {
useEffect(() => {
let stale = false
if (listImageUri) {

View File

@@ -4,7 +4,7 @@ import { FeeAmount } from '@uniswap/v3-sdk'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import ms from 'ms.macro'
import { useMemo } from 'react'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import { useFeeTierDistributionQuery } from 'state/data/enhanced'
import { FeeTierDistributionQuery } from 'state/data/generated'
@@ -112,9 +112,7 @@ function usePoolTVL(token0: Token | undefined, token1: Token | undefined) {
}
if (latestBlock - (_meta?.block?.number ?? 0) > MAX_DATA_BLOCK_AGE) {
ReactGA.exception({
description: `Graph stale (latest block: ${latestBlock})`,
})
ReactGA.event('exception', { description: `Graph stale (latest block: ${latestBlock})` })
return {
isLoading,

View File

@@ -12,13 +12,14 @@ function isWindowVisible() {
* Returns whether the window is currently visible to the user.
*/
export default function useIsWindowVisible(): boolean {
const [focused, setFocused] = useState<boolean>(isWindowVisible())
const [focused, setFocused] = useState<boolean>(false)
const listener = useCallback(() => {
setFocused(isWindowVisible())
}, [setFocused])
useEffect(() => {
if (!isVisibilityStateSupported()) return undefined
setFocused((focused) => isWindowVisible())
document.addEventListener('visibilitychange', listener)
return () => {

View File

@@ -3,7 +3,7 @@ import { LocationDescriptor } from 'history'
import useParsedQueryString from 'hooks/useParsedQueryString'
import { stringify } from 'qs'
import { useMemo } from 'react'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import { useLocation } from 'react-router-dom'
import { useActiveLocale } from './useActiveLocale'

View File

@@ -1,7 +1,7 @@
import { skipToken } from '@reduxjs/toolkit/query/react'
import { Currency } from '@uniswap/sdk-core'
import { ChainId } from '@uniswap/smart-order-router'
import { FeeAmount, nearestUsableTick, Pool, TICK_SPACINGS, tickToPrice } from '@uniswap/v3-sdk'
import { SupportedChainId } from 'constants/chains'
import { ZERO_ADDRESS } from 'constants/misc'
import JSBI from 'jsbi'
import { useSingleContractMultipleData } from 'lib/hooks/multicall'
@@ -14,7 +14,7 @@ import { useTickLens } from './useContract'
import { PoolState, usePool } from './usePools'
const PRICE_FIXED_DIGITS = 8
const CHAIN_IDS_MISSING_SUBGRAPH_DATA = [ChainId.ARBITRUM_ONE, ChainId.ARBITRUM_RINKEBY]
const CHAIN_IDS_MISSING_SUBGRAPH_DATA = [SupportedChainId.ARBITRUM_ONE, SupportedChainId.ARBITRUM_RINKEBY]
export interface TickData {
tick: number

View File

@@ -1,6 +1,6 @@
import { Interface } from '@ethersproject/abi'
import { BigintIsh, Currency, Token } from '@uniswap/sdk-core'
import IUniswapV3PoolStateJson from '@uniswap/v3-core/artifacts/contracts/interfaces/pool/IUniswapV3PoolState.sol/IUniswapV3PoolState.json'
import { abi as IUniswapV3PoolStateABI } from '@uniswap/v3-core/artifacts/contracts/interfaces/pool/IUniswapV3PoolState.sol/IUniswapV3PoolState.json'
import { computePoolAddress } from '@uniswap/v3-sdk'
import { FeeAmount, Pool } from '@uniswap/v3-sdk'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
@@ -11,8 +11,6 @@ import { useMemo } from 'react'
import { V3_CORE_FACTORY_ADDRESSES } from '../constants/addresses'
import { IUniswapV3PoolStateInterface } from '../types/v3/IUniswapV3PoolState'
const { abi: IUniswapV3PoolStateABI } = IUniswapV3PoolStateJson
const POOL_STATE_INTERFACE = new Interface(IUniswapV3PoolStateABI) as IUniswapV3PoolStateInterface
// Classes are expensive to instantiate, so this caches the recently instantiated pools.

View File

@@ -1,7 +1,7 @@
import { Currency, CurrencyAmount, Price, Token, TradeType } from '@uniswap/sdk-core'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { useMemo } from 'react'
import { useMemo, useRef } from 'react'
import { SupportedChainId } from '../constants/chains'
import { DAI_OPTIMISM, USDC_ARBITRUM, USDC_MAINNET, USDC_POLYGON } from '../constants/tokens'
@@ -32,8 +32,7 @@ export default function useUSDCPrice(currency?: Currency): Price<Currency, Token
maxHops: 2,
})
const v3USDCTrade = useClientSideV3Trade(TradeType.EXACT_OUTPUT, amountOut, currency)
return useMemo(() => {
const price = useMemo(() => {
if (!currency || !stablecoin) {
return undefined
}
@@ -54,6 +53,12 @@ export default function useUSDCPrice(currency?: Currency): Price<Currency, Token
return undefined
}, [currency, stablecoin, v2USDCTrade, v3USDCTrade.trade])
const lastPrice = useRef(price)
if (!price || !lastPrice.current || !price.equalTo(lastPrice.current)) {
lastPrice.current = price
}
return lastPrice.current
}
export function useUSDCValue(currencyAmount: CurrencyAmount<Currency> | undefined | null) {

View File

@@ -1,14 +1,12 @@
import { Interface } from '@ethersproject/abi'
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import IUniswapV2PairJson from '@uniswap/v2-core/build/IUniswapV2Pair.json'
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
import { computePairAddress, Pair } from '@uniswap/v2-sdk'
import { useMultipleContractSingleData } from 'lib/hooks/multicall'
import { useMemo } from 'react'
import { V2_FACTORY_ADDRESSES } from '../constants/addresses'
const { abi: IUniswapV2PairABI } = IUniswapV2PairJson
const PAIR_INTERFACE = new Interface(IUniswapV2PairABI)
export enum PairState {

View File

@@ -23,13 +23,11 @@ export function useV3PositionFees(
const tokenIdHexString = tokenId?.toHexString()
const latestBlockNumber = useBlockNumber()
// TODO find a way to get this into multicall
// we can't use multicall for this because we need to simulate the call from a specific address
// latestBlockNumber is included to ensure data stays up-to-date every block
const [amounts, setAmounts] = useState<[BigNumber, BigNumber]>()
const [amounts, setAmounts] = useState<[BigNumber, BigNumber] | undefined>()
useEffect(() => {
let stale = false
if (positionManager && tokenIdHexString && owner && typeof latestBlockNumber === 'number') {
if (positionManager && tokenIdHexString && owner) {
positionManager.callStatic
.collect(
{
@@ -41,19 +39,15 @@ export function useV3PositionFees(
{ from: owner } // need to simulate the call as the owner
)
.then((results) => {
if (!stale) setAmounts([results.amount0, results.amount1])
setAmounts([results.amount0, results.amount1])
})
}
return () => {
stale = true
}
}, [positionManager, tokenIdHexString, owner, latestBlockNumber])
if (pool && amounts) {
return [
CurrencyAmount.fromRawAmount(!asWETH ? unwrappedToken(pool.token0) : pool.token0, amounts[0].toString()),
CurrencyAmount.fromRawAmount(!asWETH ? unwrappedToken(pool.token1) : pool.token1, amounts[1].toString()),
CurrencyAmount.fromRawAmount(asWETH ? pool.token0 : unwrappedToken(pool.token0), amounts[0].toString()),
CurrencyAmount.fromRawAmount(asWETH ? pool.token1 : unwrappedToken(pool.token1), amounts[1].toString()),
]
} else {
return [undefined, undefined]

View File

@@ -3,7 +3,7 @@ import 'inter-ui'
import 'polyfills'
import 'components/analytics'
import { BlockUpdater } from 'lib/hooks/useBlockNumber'
import { BlockNumberProvider } from 'lib/hooks/useBlockNumber'
import { MulticallUpdater } from 'lib/state/multicall'
import { StrictMode } from 'react'
import ReactDOM from 'react-dom'
@@ -40,7 +40,6 @@ function Updaters() {
<UserUpdater />
<ApplicationUpdater />
<TransactionUpdater />
<BlockUpdater />
<MulticallUpdater />
<LogsUpdater />
</>
@@ -55,11 +54,13 @@ ReactDOM.render(
<Web3ReactProvider getLibrary={getLibrary}>
<Web3ProviderNetwork getLibrary={getLibrary}>
<Blocklist>
<Updaters />
<ThemeProvider>
<ThemedGlobalStyle />
<App />
</ThemeProvider>
<BlockNumberProvider>
<Updaters />
<ThemeProvider>
<ThemedGlobalStyle />
<App />
</ThemeProvider>
</BlockNumberProvider>
</Blocklist>
</Web3ProviderNetwork>
</Web3ReactProvider>

View File

@@ -10,6 +10,10 @@
{
"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": [

View File

@@ -5,17 +5,7 @@ import { ReactNode, useMemo } from 'react'
import Button from './Button'
import Row from './Row'
const fadeIn = keyframes`
from {
opacity: 0;
}
to {
opacity: 1;
}
`
const StyledButton = styled(Button)`
animation: ${fadeIn} 0.25s ease-in;
border-radius: ${({ theme }) => theme.borderRadius * 0.75}em;
flex-grow: 1;
transition: background-color 0.25s ease-out, border-radius 0.25s ease-out, flex-grow 0.25s ease-out;

View File

@@ -1,5 +1,5 @@
import { Icon } from 'lib/icons'
import styled, { Color } from 'lib/theme'
import styled, { Color, css } from 'lib/theme'
import { ComponentProps, forwardRef } from 'react'
export const BaseButton = styled.button`
@@ -15,17 +15,26 @@ export const BaseButton = styled.button`
margin: 0;
padding: 0;
:enabled {
transition: filter 0.125s linear;
}
:disabled {
cursor: initial;
filter: saturate(0) opacity(0.4);
}
`
const transitionCss = css`
transition: background-color 0.125s linear, border-color 0.125s linear, filter 0.125s linear;
`
export default styled(BaseButton)<{ color?: Color }>`
export default styled(BaseButton)<{ color?: Color; transition?: boolean }>`
border: 1px solid transparent;
color: ${({ color = 'interactive', theme }) => color === 'interactive' && theme.onInteractive};
:enabled {
background-color: ${({ color = 'interactive', theme }) => theme[color]};
${({ transition = true }) => transition && transitionCss};
}
:enabled:hover {
@@ -33,9 +42,8 @@ export default styled(BaseButton)<{ color?: Color }>`
}
:disabled {
border: 1px solid ${({ theme }) => theme.outline};
border-color: ${({ theme }) => theme.outline};
color: ${({ theme }) => theme.secondary};
cursor: initial;
}
`

View File

@@ -1,30 +0,0 @@
import { SUPPORTED_LOCALES } from 'constants/locales'
import { WidgetProps } from 'lib/components/Widget'
import { IntegrationError } from 'lib/errors'
import { PropsWithChildren, useEffect } from 'react'
export default function WidgetsPropsValidator(props: PropsWithChildren<WidgetProps>) {
const { jsonRpcEndpoint, provider } = props
useEffect(() => {
if (!provider && !jsonRpcEndpoint) {
throw new IntegrationError('This widget requires a provider or jsonRpcEndpoint.')
}
}, [provider, jsonRpcEndpoint])
const { width } = props
useEffect(() => {
if (width && width < 300) {
throw new IntegrationError(`Set widget width to at least 300px. (You set it to ${width}.)`)
}
}, [width])
const { locale } = props
useEffect(() => {
if (locale && locale !== 'pseudo' && !SUPPORTED_LOCALES.includes(locale)) {
console.warn('Unsupported locale: ', locale)
}
}, [locale])
return <>{props.children}</>
}

View File

@@ -1,4 +1,6 @@
import { loadingOpacity } from 'lib/css/loading'
import styled, { css } from 'lib/theme'
import { transparentize } from 'polished'
import { ChangeEvent, forwardRef, HTMLProps, useCallback } from 'react'
const Input = styled.input`
@@ -34,6 +36,16 @@ const Input = styled.input`
::placeholder {
color: ${({ theme }) => theme.secondary};
}
:enabled {
transition: color 0.125s linear;
}
:disabled {
// Overrides WebKit's override of input:disabled color.
-webkit-text-fill-color: ${({ theme }) => transparentize(1 - loadingOpacity, theme.primary)};
color: ${({ theme }) => transparentize(1 - loadingOpacity, theme.primary)};
}
`
export default Input

View File

@@ -80,7 +80,7 @@ export default function Input({ disabled, focused }: InputProps) {
// extract eagerly in case of reversal
usePrefetchCurrencyColor(inputCurrency)
const isRouteLoading = tradeState === TradeState.SYNCING || tradeState === TradeState.LOADING
const isRouteLoading = disabled || tradeState === TradeState.SYNCING || tradeState === TradeState.LOADING
const isDependentField = !useIsSwapFieldIndependent(Field.INPUT)
const isLoading = isRouteLoading && isDependentField

View File

@@ -46,7 +46,7 @@ export default function Output({ disabled, focused, children }: PropsWithChildre
const [swapOutputAmount, updateSwapOutputAmount] = useSwapAmount(Field.OUTPUT)
const [swapOutputCurrency, updateSwapOutputCurrency] = useSwapCurrency(Field.OUTPUT)
const isRouteLoading = tradeState === TradeState.SYNCING || tradeState === TradeState.LOADING
const isRouteLoading = disabled || tradeState === TradeState.SYNCING || tradeState === TradeState.LOADING
const isDependentField = !useIsSwapFieldIndependent(Field.OUTPUT)
const isLoading = isRouteLoading && isDependentField

View File

@@ -33,6 +33,7 @@ const Overlay = styled.div`
const StyledReverseButton = styled(Button)<{ turns: number }>`
border-radius: ${({ theme }) => theme.borderRadius * 0.75}em;
color: ${({ theme }) => theme.primary};
height: 2.5em;
position: relative;
width: 2.5em;

View File

@@ -3,7 +3,7 @@ import { SupportedChainId } from 'constants/chains'
import { nativeOnChain } from 'constants/tokens'
import { useUpdateAtom } from 'jotai/utils'
import { useSwapInfo } from 'lib/hooks/swap'
import { SwapInfoUpdater } from 'lib/hooks/swap/useSwapInfo'
import { SwapInfoProvider } from 'lib/hooks/swap/useSwapInfo'
import { Field, swapAtom } from 'lib/state/swap'
import { useEffect } from 'react'
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
@@ -54,7 +54,8 @@ function Fixture() {
export default (
<>
<SwapInfoUpdater />
<Fixture />
<SwapInfoProvider>
<Fixture />
</SwapInfoProvider>
</>
)

View File

@@ -1,4 +1,3 @@
import { tokens } from '@uniswap/default-token-list'
import { DAI, USDC_MAINNET } from 'constants/tokens'
import { useUpdateAtom } from 'jotai/utils'
import { useEffect } from 'react'
@@ -65,7 +64,6 @@ function Fixture() {
defaultInputAmount={defaultInputAmount}
defaultOutputTokenAddress={optionsToAddressMap[defaultOutputToken]}
defaultOutputAmount={defaultOutputAmount}
tokenList={tokens}
onConnectWallet={() => console.log('onConnectWallet')} // this handler is included as a test of functionality, but only logs
/>
)

View File

@@ -14,6 +14,7 @@ import { TransactionType } from 'lib/state/transactions'
import { useTheme } from 'lib/theme'
import { isAnimating } from 'lib/utils/animations'
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
import { TradeState } from 'state/routing/types'
import invariant from 'tiny-invariant'
import ActionButton, { ActionButtonProps } from '../../ActionButton'
@@ -152,13 +153,17 @@ export default memo(function SwapButton({ disabled }: SwapButtonProps) {
if (disableSwap) {
return { disabled: true }
} else if (wrapType === WrapType.NONE) {
return approvalAction ? { action: approvalAction } : { onClick: () => setOpen(true) }
return approvalAction
? { action: approvalAction }
: trade.state === TradeState.VALID
? { onClick: () => setOpen(true) }
: { disabled: true }
} else {
return isPending
? { action: { message: <Trans>Confirm in your wallet</Trans>, icon: Spinner } }
: { onClick: onWrap }
}
}, [approvalAction, disableSwap, isPending, onWrap, wrapType])
}, [approvalAction, disableSwap, isPending, onWrap, trade.state, wrapType])
const Label = useCallback(() => {
switch (wrapType) {
case WrapType.UNWRAP:

View File

@@ -18,7 +18,8 @@ const TokenInputRow = styled(Row)`
const ValueInput = styled(DecimalInput)`
color: ${({ theme }) => theme.primary};
height: 1em;
height: 1.5em;
margin: -0.25em 0;
:hover:not(:focus-within) {
color: ${({ theme }) => theme.onHover(theme.primary)};

View File

@@ -32,6 +32,19 @@ function Caption({ icon: Icon = AlertTriangle, caption }: CaptionProps) {
)
}
export function Connecting() {
return (
<Caption
icon={InlineSpinner}
caption={
<Loading>
<Trans>Connecting</Trans>
</Loading>
}
/>
)
}
export function ConnectWallet() {
return <Caption caption={<Trans>Connect wallet to swap</Trans>} />
}
@@ -48,6 +61,10 @@ export function InsufficientLiquidity() {
return <Caption caption={<Trans>Insufficient liquidity in the pool for your trade</Trans>} />
}
export function Error() {
return <Caption caption={<Trans>Error fetching trade</Trans>} />
}
export function Empty() {
return <Caption icon={Info} caption={<Trans>Enter an amount</Trans>} />
}

View File

@@ -17,58 +17,60 @@ const ToolbarRow = styled(Row)`
${largeIconCss}
`
export default memo(function Toolbar({ disabled }: { disabled?: boolean }) {
const { chainId } = useActiveWeb3React()
export default memo(function Toolbar() {
const { active, activating, chainId } = useActiveWeb3React()
const {
[Field.INPUT]: { currency: inputCurrency, balance: inputBalance, amount: inputAmount },
[Field.OUTPUT]: { currency: outputCurrency, usdc: outputUSDC },
trade: { trade, state },
impact,
} = useSwapInfo()
const isRouteLoading = state === TradeState.SYNCING || state === TradeState.LOADING
const isAmountPopulated = useIsAmountPopulated()
const { type: wrapType } = useWrapCallback()
const caption = useMemo(() => {
if (disabled) {
if (!active || !chainId) {
if (activating) return <Caption.Connecting />
return <Caption.ConnectWallet />
}
if (chainId && !ALL_SUPPORTED_CHAIN_IDS.includes(chainId)) {
if (!ALL_SUPPORTED_CHAIN_IDS.includes(chainId)) {
return <Caption.UnsupportedNetwork />
}
if (inputCurrency && outputCurrency && isAmountPopulated) {
if (state === TradeState.SYNCING || state === TradeState.LOADING) {
return <Caption.LoadingTrade />
}
if (inputBalance && inputAmount?.greaterThan(inputBalance)) {
return <Caption.InsufficientBalance currency={inputCurrency} />
}
if (wrapType !== WrapType.NONE) {
return <Caption.WrapCurrency inputCurrency={inputCurrency} outputCurrency={outputCurrency} />
}
if (isRouteLoading) {
return <Caption.LoadingTrade />
if (state === TradeState.NO_ROUTE_FOUND || (trade && !trade.swaps)) {
return <Caption.InsufficientLiquidity />
}
if (trade) {
if (!trade.swaps) {
return <Caption.InsufficientLiquidity />
}
if (trade.inputAmount && trade.outputAmount) {
return <Caption.Trade trade={trade} outputUSDC={outputUSDC} impact={impact} />
}
if (trade?.inputAmount && trade.outputAmount) {
return <Caption.Trade trade={trade} outputUSDC={outputUSDC} impact={impact} />
}
if (state === TradeState.INVALID) {
return <Caption.Error />
}
}
return <Caption.Empty />
}, [
activating,
active,
chainId,
disabled,
impact,
inputAmount,
inputBalance,
inputCurrency,
isAmountPopulated,
isRouteLoading,
outputCurrency,
outputUSDC,
state,
trade,
wrapType,
])

View File

@@ -1,17 +1,15 @@
import { Trans } from '@lingui/macro'
import { TokenInfo } from '@uniswap/token-lists'
import { useAtom } from 'jotai'
import { SwapInfoUpdater } from 'lib/hooks/swap/useSwapInfo'
import { SwapInfoProvider } from 'lib/hooks/swap/useSwapInfo'
import useSyncConvenienceFee, { FeeOptions } from 'lib/hooks/swap/useSyncConvenienceFee'
import useSyncTokenDefaults, { TokenDefaults } from 'lib/hooks/swap/useSyncTokenDefaults'
import { usePendingTransactions } from 'lib/hooks/transactions'
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
import useHasFocus from 'lib/hooks/useHasFocus'
import useOnSupportedNetwork from 'lib/hooks/useOnSupportedNetwork'
import useTokenList, { useSyncTokenList } from 'lib/hooks/useTokenList'
import { displayTxHashAtom } from 'lib/state/swap'
import { SwapTransactionInfo, Transaction, TransactionType, WrapTransactionInfo } from 'lib/state/transactions'
import { useMemo, useState } from 'react'
import { useState } from 'react'
import Dialog from '../Dialog'
import Header from '../Header'
@@ -23,8 +21,8 @@ import ReverseButton from './ReverseButton'
import Settings from './Settings'
import { StatusDialog } from './Status'
import SwapButton from './SwapButton'
import SwapPropValidator from './SwapPropValidator'
import Toolbar from './Toolbar'
import useValidate from './useValidate'
function getTransactionFromMap(
txs: { [hash: string]: Transaction },
@@ -43,14 +41,13 @@ function getTransactionFromMap(
}
export interface SwapProps extends TokenDefaults, FeeOptions {
tokenList?: string | TokenInfo[]
onConnectWallet?: () => void
}
export default function Swap(props: SwapProps) {
useSyncTokenList(props.tokenList)
useSyncTokenDefaults(props)
useValidate(props)
useSyncConvenienceFee(props)
useSyncTokenDefaults(props)
const { active, account } = useActiveWeb3React()
const [wrapper, setWrapper] = useState<HTMLDivElement | null>(null)
@@ -59,32 +56,27 @@ export default function Swap(props: SwapProps) {
const pendingTxs = usePendingTransactions()
const displayTx = getTransactionFromMap(pendingTxs, displayTxHash)
const tokenList = useTokenList()
const onSupportedNetwork = useOnSupportedNetwork()
const isSwapSupported = useMemo(
() => Boolean(active && onSupportedNetwork && tokenList?.length),
[active, onSupportedNetwork, tokenList?.length]
)
const isDisabled = !(active && onSupportedNetwork)
const focused = useHasFocus(wrapper)
const isInteractive = Boolean(active && onSupportedNetwork)
return (
<SwapPropValidator {...props}>
{isSwapSupported && <SwapInfoUpdater />}
<>
<Header title={<Trans>Swap</Trans>}>
{active && <Wallet disabled={!account} onClick={props.onConnectWallet} />}
<Settings disabled={!isInteractive} />
<Settings disabled={isDisabled} />
</Header>
<div ref={setWrapper}>
<BoundaryProvider value={wrapper}>
<Input disabled={!isInteractive} focused={focused} />
<ReverseButton disabled={!isInteractive} />
<Output disabled={!isInteractive} focused={focused}>
<Toolbar disabled={!active} />
<SwapButton disabled={!isSwapSupported} />
</Output>
<SwapInfoProvider disabled={isDisabled}>
<Input disabled={isDisabled} focused={focused} />
<ReverseButton disabled={isDisabled} />
<Output disabled={isDisabled} focused={focused}>
<Toolbar />
<SwapButton disabled={isDisabled} />
</Output>
</SwapInfoProvider>
</BoundaryProvider>
</div>
{displayTx && (
@@ -92,6 +84,6 @@ export default function Swap(props: SwapProps) {
<StatusDialog tx={displayTx} onClose={() => setDisplayTxHash()} />
</Dialog>
)}
</SwapPropValidator>
</>
)
}

View File

@@ -1,4 +1,3 @@
import { BigNumber } from '@ethersproject/bignumber'
import { IntegrationError } from 'lib/errors'
import { FeeOptions } from 'lib/hooks/swap/useSyncConvenienceFee'
import { DefaultAddress, TokenDefaults } from 'lib/hooks/swap/useSyncTokenDefaults'
@@ -18,12 +17,12 @@ function isAddressOrAddressMap(addressOrMap: DefaultAddress): boolean {
type ValidatorProps = PropsWithChildren<TokenDefaults & FeeOptions>
export default function SwapPropValidator(props: ValidatorProps) {
export default function useValidate(props: ValidatorProps) {
const { convenienceFee, convenienceFeeRecipient } = props
useEffect(() => {
if (convenienceFee) {
if (convenienceFee > 100 || convenienceFee < 0) {
throw new IntegrationError(`convenienceFee must be between 0 and 100. (You set it to ${convenienceFee})`)
throw new IntegrationError(`convenienceFee must be between 0 and 100 (you set it to ${convenienceFee}).`)
}
if (!convenienceFeeRecipient) {
throw new IntegrationError('convenienceFeeRecipient is required when convenienceFee is set.')
@@ -32,7 +31,7 @@ export default function SwapPropValidator(props: ValidatorProps) {
if (typeof convenienceFeeRecipient === 'string') {
if (!isAddress(convenienceFeeRecipient)) {
throw new IntegrationError(
`convenienceFeeRecipient must be a valid address. (You set it to ${convenienceFeeRecipient}.)`
`convenienceFeeRecipient must be a valid address (you set it to ${convenienceFeeRecipient}).`
)
}
} else if (typeof convenienceFeeRecipient === 'object') {
@@ -40,7 +39,7 @@ export default function SwapPropValidator(props: ValidatorProps) {
if (!isAddress(recipient)) {
const values = Object.values(convenienceFeeRecipient).join(', ')
throw new IntegrationError(
`All values in convenienceFeeRecipient object must be valid addresses. (You used ${values}.)`
`All values in convenienceFeeRecipient object must be valid addresses (you used ${values}).`
)
}
})
@@ -48,26 +47,30 @@ export default function SwapPropValidator(props: ValidatorProps) {
}
}, [convenienceFee, convenienceFeeRecipient])
const { defaultInputTokenAddress, defaultInputAmount, defaultOutputTokenAddress, defaultOutputAmount } = props
const { defaultInputAmount, defaultOutputAmount } = props
useEffect(() => {
if (defaultOutputAmount && defaultInputAmount) {
throw new IntegrationError('defaultInputAmount and defaultOutputAmount may not both be defined.')
}
if (defaultInputAmount && BigNumber.from(defaultInputAmount).lt(0)) {
throw new IntegrationError(`defaultInputAmount must be a positive number. (You set it to ${defaultInputAmount})`)
if (defaultInputAmount && (isNaN(+defaultInputAmount) || defaultInputAmount < 0)) {
throw new IntegrationError(`defaultInputAmount must be a positive number (you set it to ${defaultInputAmount})`)
}
if (defaultOutputAmount && BigNumber.from(defaultOutputAmount).lt(0)) {
if (defaultOutputAmount && (isNaN(+defaultOutputAmount) || defaultOutputAmount < 0)) {
throw new IntegrationError(
`defaultOutputAmount must be a positive number. (You set it to ${defaultOutputAmount})`
`defaultOutputAmount must be a positive number (you set it to ${defaultOutputAmount}).`
)
}
}, [defaultInputAmount, defaultOutputAmount])
const { defaultInputTokenAddress, defaultOutputTokenAddress } = props
useEffect(() => {
if (
defaultInputTokenAddress &&
!isAddressOrAddressMap(defaultInputTokenAddress) &&
defaultInputTokenAddress !== 'NATIVE'
) {
throw new IntegrationError(
`defaultInputTokenAddress(es) must be a valid address or "NATIVE". (You set it to ${defaultInputTokenAddress}`
`defaultInputTokenAddress must be a valid address or "NATIVE" (you set it to ${defaultInputTokenAddress}).`
)
}
if (
@@ -76,10 +79,8 @@ export default function SwapPropValidator(props: ValidatorProps) {
defaultOutputTokenAddress !== 'NATIVE'
) {
throw new IntegrationError(
`defaultOutputTokenAddress(es) must be a valid address or "NATIVE". (You set it to ${defaultOutputTokenAddress}`
`defaultOutputTokenAddress must be a valid address or "NATIVE" (you set it to ${defaultOutputTokenAddress}).`
)
}
}, [defaultInputTokenAddress, defaultInputAmount, defaultOutputTokenAddress, defaultOutputAmount])
return <>{props.children}</>
}, [defaultInputTokenAddress, defaultOutputTokenAddress])
}

View File

@@ -26,16 +26,17 @@ function TokenImg({ token, ...rest }: TokenImgProps) {
setAttempt((attempt) => ++attempt)
}, [])
return useMemo(() => {
const src = useMemo(() => {
// Trigger a re-render when an error occurs.
void attempt
const src = srcs.find((src) => !badSrcs.has(src))
if (!src) return <MissingToken color="secondary" {...rest} />
return srcs.find((src) => !badSrcs.has(src))
}, [attempt, srcs])
const alt = tokenInfo.name || tokenInfo.symbol
return <img src={src} alt={alt} key={alt} onError={onError} {...rest} />
}, [attempt, onError, rest, srcs, tokenInfo.name, tokenInfo.symbol])
if (!src) return <MissingToken color="secondary" {...rest} />
const alt = tokenInfo.name || tokenInfo.symbol
return <img src={src} alt={alt} key={alt} onError={onError} {...rest} />
}
export default styled(TokenImg)<{ size?: number }>`

View File

@@ -1,15 +1,15 @@
import DEFAULT_TOKEN_LIST from '@uniswap/default-token-list'
import { useSyncTokenList } from 'lib/hooks/useTokenList'
import { TokenListProvider } from 'lib/hooks/useTokenList'
import { Modal } from './Dialog'
import { TokenSelectDialog } from './TokenSelect'
export default function Fixture() {
useSyncTokenList(DEFAULT_TOKEN_LIST.tokens)
return (
<Modal color="module">
<TokenSelectDialog onSelect={() => void 0} />
<TokenListProvider list={DEFAULT_TOKEN_LIST.tokens}>
<TokenSelectDialog onSelect={() => void 0} />
</TokenListProvider>
</Modal>
)
}

View File

@@ -1,29 +1,33 @@
import { Trans } from '@lingui/macro'
import { Currency } from '@uniswap/sdk-core'
import { ChevronDown } from 'lib/icons'
import styled, { ThemedText } from 'lib/theme'
import { useMemo } from 'react'
import styled, { css, ThemedText } from 'lib/theme'
import { useEffect, useMemo, useState } from 'react'
import Button from '../Button'
import Row from '../Row'
import TokenImg from '../TokenImg'
const StyledTokenButton = styled(Button)<{ empty?: boolean }>`
const transitionCss = css`
transition: background-color 0.125s linear, border-color 0.125s linear, filter 0.125s linear, width 0.125s ease-out;
`
const StyledTokenButton = styled(Button)`
border-radius: ${({ theme }) => theme.borderRadius}em;
padding: 0.25em;
padding-left: ${({ empty }) => (empty ? 0.75 : 0.25)}em;
:disabled {
// prevents border from expanding the button's box size
padding: calc(0.25em - 1px);
padding-left: calc(${({ empty }) => (empty ? 0.75 : 0.25)}em - 1px);
:enabled {
${({ transition }) => transition && transitionCss};
}
`
const TokenButtonRow = styled(Row)<{ collapsed: boolean }>`
const TokenButtonRow = styled(Row)<{ empty: boolean; collapsed: boolean }>`
float: right;
height: 1.2em;
// max-width must have an absolute value in order to transition.
max-width: ${({ collapsed }) => (collapsed ? '1.2em' : '12em')};
padding-left: ${({ empty }) => empty && 0.5}em;
width: fit-content;
overflow: hidden;
transition: max-width 0.25s linear;
@@ -42,10 +46,42 @@ interface TokenButtonProps {
export default function TokenButton({ value, collapsed, disabled, onClick }: TokenButtonProps) {
const buttonBackgroundColor = useMemo(() => (value ? 'interactive' : 'accent'), [value])
const contentColor = useMemo(() => (value || disabled ? 'onInteractive' : 'onAccent'), [value, disabled])
// Transition the button only if transitioning from a disabled state.
// This makes initialization cleaner without adding distracting UX to normal swap flows.
const [shouldTransition, setShouldTransition] = useState(disabled)
useEffect(() => {
if (disabled) {
setShouldTransition(true)
}
}, [disabled])
// width must have an absolute value in order to transition, so it is taken from the row ref.
const [row, setRow] = useState<HTMLDivElement | null>(null)
const style = useMemo(() => {
if (!shouldTransition) return
return { width: row ? row.clientWidth + /* padding= */ 8 + /* border= */ 2 : undefined }
}, [row, shouldTransition])
return (
<StyledTokenButton onClick={onClick} empty={!value} color={buttonBackgroundColor} disabled={disabled}>
<StyledTokenButton
onClick={onClick}
color={buttonBackgroundColor}
disabled={disabled}
style={style}
transition={shouldTransition}
onTransitionEnd={() => setShouldTransition(false)}
>
<ThemedText.ButtonLarge color={contentColor}>
<TokenButtonRow gap={0.4} collapsed={Boolean(value) && collapsed}>
<TokenButtonRow
gap={0.4}
empty={!value}
collapsed={collapsed}
// ref is used to set an absolute width, so it must be reset for each value passed.
// To force this, value?.symbol is passed as a key.
ref={setRow}
key={value?.symbol}
>
{value ? (
<>
<TokenImg token={value} size={1.2} />

View File

@@ -25,9 +25,9 @@ const SearchInput = styled(StringInput)`
function usePrefetchBalances() {
const { account } = useActiveWeb3React()
const tokenList = useTokenList()
const [prefetchedTokenList, setPrefetchedTokenList] = useState(tokenList)
useEffect(() => setPrefetchedTokenList(tokenList), [tokenList])
useCurrencyBalances(account, tokenList !== prefetchedTokenList ? tokenList : undefined)
const prefetchedTokenList = useRef<typeof tokenList>()
useCurrencyBalances(account, tokenList !== prefetchedTokenList.current ? tokenList : undefined)
prefetchedTokenList.current = tokenList
}
function useAreBalancesLoaded(): boolean {

View File

@@ -1,21 +1,21 @@
import { Provider as EthersProvider } from '@ethersproject/abstract-provider'
import { JsonRpcProvider } from '@ethersproject/providers'
import { TokenInfo } from '@uniswap/token-lists'
import { Provider as Eip1193Provider } from '@web3-react/types'
import { DEFAULT_LOCALE, SupportedLocale } from 'constants/locales'
import { DEFAULT_LOCALE, SUPPORTED_LOCALES, SupportedLocale } from 'constants/locales'
import { Provider as AtomProvider } from 'jotai'
import { TransactionsUpdater } from 'lib/hooks/transactions'
import { Web3Provider } from 'lib/hooks/useActiveWeb3React'
import { BlockUpdater } from 'lib/hooks/useBlockNumber'
import useEip1193Provider from 'lib/hooks/useEip1193Provider'
import { ActiveWeb3Provider } from 'lib/hooks/useActiveWeb3React'
import { BlockNumberProvider } from 'lib/hooks/useBlockNumber'
import { TokenListProvider } from 'lib/hooks/useTokenList'
import { Provider as I18nProvider } from 'lib/i18n'
import { MulticallUpdater, store as multicallStore } from 'lib/state/multicall'
import styled, { keyframes, Theme, ThemeProvider } from 'lib/theme'
import { UNMOUNTING } from 'lib/utils/animations'
import { PropsWithChildren, StrictMode, useState } from 'react'
import { PropsWithChildren, StrictMode, useMemo, useState } from 'react'
import { Provider as ReduxProvider } from 'react-redux'
import { Modal, Provider as DialogProvider } from './Dialog'
import ErrorBoundary, { ErrorHandler } from './Error/ErrorBoundary'
import WidgetPropValidator from './Error/WidgetsPropsValidator'
const WidgetWrapper = styled.div<{ width?: number | string }>`
-moz-osx-font-smoothing: grayscale;
@@ -31,7 +31,7 @@ const WidgetWrapper = styled.div<{ width?: number | string }>`
font-size: 16px;
font-smooth: always;
font-variant: none;
height: 356px;
height: 360px;
min-width: 300px;
padding: 0.25em;
position: relative;
@@ -82,21 +82,12 @@ const DialogWrapper = styled.div`
}
`
function Updaters() {
return (
<>
<BlockUpdater />
<MulticallUpdater />
<TransactionsUpdater />
</>
)
}
export type WidgetProps = {
theme?: Theme
locale?: SupportedLocale
provider?: Eip1193Provider | EthersProvider
jsonRpcEndpoint?: string
provider?: Eip1193Provider | JsonRpcProvider
jsonRpcEndpoint?: string | JsonRpcProvider
tokenList?: string | TokenInfo[]
width?: string | number
dialog?: HTMLElement | null
className?: string
@@ -104,42 +95,47 @@ export type WidgetProps = {
}
export default function Widget(props: PropsWithChildren<WidgetProps>) {
const {
children,
theme,
locale = DEFAULT_LOCALE,
provider,
jsonRpcEndpoint,
width = 360,
dialog: userDialog,
className,
onError,
} = props
const eip1193 = useEip1193Provider(provider)
const { children, theme, provider, jsonRpcEndpoint, dialog: userDialog, className, onError } = props
const width = useMemo(() => {
if (props.width && props.width < 300) {
console.warn(`Widget width must be at least 300px (you set it to ${props.width}). Falling back to 300px.`)
return 300
}
return props.width ?? 360
}, [props.width])
const locale = useMemo(() => {
if (props.locale && ![...SUPPORTED_LOCALES, 'pseudo'].includes(props.locale)) {
console.warn(`Unsupported locale: ${props.locale}. Falling back to ${DEFAULT_LOCALE}.`)
return DEFAULT_LOCALE
}
return props.locale ?? DEFAULT_LOCALE
}, [props.locale])
const [dialog, setDialog] = useState<HTMLDivElement | null>(null)
return (
<StrictMode>
<I18nProvider locale={locale}>
<ThemeProvider theme={theme}>
<WidgetWrapper width={width} className={className}>
<ThemeProvider theme={theme}>
<WidgetWrapper width={width} className={className}>
<I18nProvider locale={locale}>
<DialogWrapper ref={setDialog} />
<DialogProvider value={userDialog || dialog}>
<ErrorBoundary onError={onError}>
<WidgetPropValidator {...props}>
<ReduxProvider store={multicallStore}>
<AtomProvider>
<Web3Provider provider={eip1193} jsonRpcEndpoint={jsonRpcEndpoint}>
<Updaters />
{children}
</Web3Provider>
</AtomProvider>
</ReduxProvider>
</WidgetPropValidator>
<ReduxProvider store={multicallStore}>
<AtomProvider>
<ActiveWeb3Provider provider={provider} jsonRpcEndpoint={jsonRpcEndpoint}>
<BlockNumberProvider>
<MulticallUpdater />
<TransactionsUpdater />
<TokenListProvider list={props.tokenList}>{children}</TokenListProvider>
</BlockNumberProvider>
</ActiveWeb3Provider>
</AtomProvider>
</ReduxProvider>
</ErrorBoundary>
</DialogProvider>
</WidgetWrapper>
</ThemeProvider>
</I18nProvider>
</I18nProvider>
</WidgetWrapper>
</ThemeProvider>
</StrictMode>
)
}

View File

@@ -1,3 +1,4 @@
import { tokens } from '@uniswap/default-token-list'
import { initializeConnector } from '@web3-react/core'
import { MetaMask } from '@web3-react/metamask'
import { Connector } from '@web3-react/types'
@@ -17,14 +18,15 @@ const [walletConnect] = initializeConnector<WalletConnect>(
export default function Wrapper({ children }: { children: ReactNode }) {
const [width] = useValue('width', { defaultValue: 360 })
const [locale] = useSelect('locale', { defaultValue: DEFAULT_LOCALE, options: ['pseudo', ...SUPPORTED_LOCALES] })
const [locale] = useSelect('locale', {
defaultValue: DEFAULT_LOCALE,
options: ['fa-KE (unsupported)', 'pseudo', ...SUPPORTED_LOCALES],
})
const [darkMode] = useValue('dark mode', { defaultValue: false })
const [theme, setTheme] = useValue('theme', { defaultValue: { ...defaultTheme, ...lightTheme } })
useEffect(() => {
setTheme({ ...defaultTheme, ...(darkMode ? darkTheme : lightTheme) })
// cosmos does not maintain referential equality for setters
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [darkMode])
}, [darkMode, setTheme])
const NO_JSON_RPC = 'None'
const [jsonRpcEndpoint] = useSelect('JSON-RPC', {
@@ -74,6 +76,7 @@ export default function Wrapper({ children }: { children: ReactNode }) {
locale={locale}
jsonRpcEndpoint={jsonRpcEndpoint === NO_JSON_RPC ? undefined : jsonRpcEndpoint}
provider={connector?.provider}
tokenList={tokens}
>
{children}
</Widget>

View File

@@ -9,6 +9,6 @@ export const loadingCss = css`
// need to use isLoading as `loading` is a reserved prop
export const loadingTransitionCss = css<{ isLoading: boolean }>`
${({ isLoading }) => isLoading && loadingCss};
transition: opacity ${({ isLoading }) => (isLoading ? 0 : 0.2)}s ease-in-out;
opacity: ${({ isLoading }) => isLoading && loadingOpacity};
transition: color 0.125s linear, opacity ${({ isLoading }) => (isLoading ? 0 : 0.25)}s ease-in-out;
`

View File

@@ -1,4 +1,6 @@
import { BigintIsh, CurrencyAmount, Token, TradeType } from '@uniswap/sdk-core'
// This file is lazy-loaded, so the import of smart-order-router is intentional.
// eslint-disable-next-line no-restricted-imports
import { AlphaRouter, AlphaRouterConfig, AlphaRouterParams, ChainId } from '@uniswap/smart-order-router'
import JSBI from 'jsbi'
import { GetQuoteResult } from 'state/routing/types'

View File

@@ -1,8 +1,9 @@
import 'setimmediate'
import { Protocol } from '@uniswap/router-sdk'
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { ChainId } from '@uniswap/smart-order-router'
import { SupportedChainId } from 'constants/chains'
import useDebounce from 'hooks/useDebounce'
import useLast from 'hooks/useLast'
import { useStablecoinAmountFromFiatValue } from 'hooks/useUSDCPrice'
import { useCallback, useMemo } from 'react'
import { GetQuoteResult, InterfaceTrade, TradeState } from 'state/routing/types'
@@ -12,7 +13,6 @@ import useWrapCallback, { WrapType } from '../swap/useWrapCallback'
import useActiveWeb3React from '../useActiveWeb3React'
import { useGetIsValidBlock } from '../useIsValidBlock'
import usePoll from '../usePoll'
import { getClientSideQuote } from './clientSideSmartOrderRouter'
import { useRoutingAPIArguments } from './useRoutingAPIArguments'
/**
@@ -20,14 +20,14 @@ import { useRoutingAPIArguments } from './useRoutingAPIArguments'
* Defaults are defined in https://github.com/Uniswap/smart-order-router/blob/309e6f6603984d3b5aef0733b0cfaf129c29f602/src/routers/alpha-router/config.ts#L83.
*/
const DistributionPercents: { [key: number]: number } = {
[ChainId.MAINNET]: 10,
[ChainId.OPTIMISM]: 10,
[ChainId.OPTIMISTIC_KOVAN]: 10,
[ChainId.ARBITRUM_ONE]: 25,
[ChainId.ARBITRUM_RINKEBY]: 25,
[SupportedChainId.MAINNET]: 10,
[SupportedChainId.OPTIMISM]: 10,
[SupportedChainId.OPTIMISTIC_KOVAN]: 10,
[SupportedChainId.ARBITRUM_ONE]: 25,
[SupportedChainId.ARBITRUM_RINKEBY]: 25,
}
const DEFAULT_DISTRIBUTION_PERCENT = 10
function getConfig(chainId: ChainId | undefined) {
function getConfig(chainId: SupportedChainId | undefined) {
return {
// Limit to only V2 and V3.
protocols: [Protocol.V2, Protocol.V3],
@@ -75,7 +75,15 @@ export default function useClientSideSmartOrderRouterTrade<TTradeType extends Tr
if (wrapType !== WrapType.NONE) return { error: undefined }
if (!queryArgs || !params) return { error: undefined }
try {
return await getClientSideQuote(queryArgs, params, config)
// Lazy-load the smart order router to improve initial pageload times.
const quoteResult = await (
await import('./clientSideSmartOrderRouter')
).getClientSideQuote(queryArgs, params, config)
// There is significant post-fetch processing, so delay a tick to prevent dropped frames.
// This is only important in the context of integrations - if we control the whole site,
// then we can afford to drop a few frames.
return new Promise((resolve) => setImmediate(() => resolve(quoteResult)))
} catch {
return { error: true }
}
@@ -84,7 +92,7 @@ export default function useClientSideSmartOrderRouterTrade<TTradeType extends Tr
const getIsValidBlock = useGetIsValidBlock()
const { data: quoteResult, error } = usePoll(getQuoteResult, JSON.stringify(queryArgs), {
debounce: isDebouncing,
staleCallback: useCallback(({ data }) => !getIsValidBlock(Number(data?.blockNumber) || 0), [getIsValidBlock]),
isStale: useCallback(({ data }) => !getIsValidBlock(Number(data?.blockNumber) || 0), [getIsValidBlock]),
}) ?? {
error: undefined,
}
@@ -105,29 +113,19 @@ export default function useClientSideSmartOrderRouterTrade<TTradeType extends Tr
}
return
}, [gasUseEstimateUSD, route, tradeType])
const lastTrade = useLast(trade, Boolean) ?? undefined
return useMemo(() => {
if (!currencyIn || !currencyOut) {
return { state: TradeState.INVALID, trade: undefined }
}
// Returns the last trade state while syncing/loading to avoid jank from clearing the last trade while loading.
if (!trade && !error) {
// Dont return last trade if currencies dont match.
const isStale =
(currencyIn && !lastTrade?.inputAmount?.currency.equals(currencyIn)) ||
(currencyOut && !lastTrade?.outputAmount?.currency.equals(currencyOut))
if (isStale) {
if (isDebouncing) {
return { state: TradeState.SYNCING, trade: undefined }
} else if (!isValid) {
return { state: TradeState.LOADING, trade: undefined }
} else if (isDebouncing) {
return { state: TradeState.SYNCING, trade: lastTrade }
}
}
if (!isValid && !error) {
return { state: TradeState.LOADING, trade: lastTrade }
}
let otherAmount = undefined
if (quoteResult) {
@@ -149,5 +147,5 @@ export default function useClientSideSmartOrderRouterTrade<TTradeType extends Tr
return { state: TradeState.VALID, trade }
}
return { state: TradeState.INVALID, trade: undefined }
}, [currencyIn, currencyOut, trade, error, isValid, quoteResult, route, isDebouncing, lastTrade, tradeType])
}, [currencyIn, currencyOut, trade, error, isValid, quoteResult, route, isDebouncing, tradeType])
}

View File

@@ -1,9 +1,13 @@
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { useClientSideV3Trade } from 'hooks/useClientSideV3Trade'
import useLast from 'hooks/useLast'
import { useMemo } from 'react'
import { InterfaceTrade, TradeState } from 'state/routing/types'
import useClientSideSmartOrderRouterTrade from '../routing/useClientSideSmartOrderRouterTrade'
export const INVALID_TRADE = { state: TradeState.INVALID, trade: undefined }
/**
* Returns the best v2+v3 trade for a desired swap.
* @param tradeType whether the swap is an exact in/out
@@ -18,15 +22,39 @@ export function useBestTrade(
state: TradeState
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined
} {
const clientSORTrade = useClientSideSmartOrderRouterTrade(tradeType, amountSpecified, otherCurrency)
const clientSORTradeObject = useClientSideSmartOrderRouterTrade(tradeType, amountSpecified, otherCurrency)
// Use a simple client side logic as backup if SOR is not available.
const useFallback = clientSORTrade.state === TradeState.NO_ROUTE_FOUND || clientSORTrade.state === TradeState.INVALID
const fallbackTrade = useClientSideV3Trade(
const useFallback =
clientSORTradeObject.state === TradeState.NO_ROUTE_FOUND || clientSORTradeObject.state === TradeState.INVALID
const fallbackTradeObject = useClientSideV3Trade(
tradeType,
useFallback ? amountSpecified : undefined,
useFallback ? otherCurrency : undefined
)
return useFallback ? fallbackTrade : clientSORTrade
const tradeObject = useFallback ? fallbackTradeObject : clientSORTradeObject
const lastTrade = useLast(tradeObject.trade, Boolean) ?? undefined
// Return the last trade while syncing/loading to avoid jank from clearing the last trade while loading.
// If the trade is unsettled and not stale, return the last trade as a placeholder during settling.
return useMemo(() => {
const { state, trade } = tradeObject
// If the trade is in a settled state, return it.
if (state === TradeState.INVALID) return INVALID_TRADE
if ((state !== TradeState.LOADING && state !== TradeState.SYNCING) || trade) return tradeObject
const [currencyIn, currencyOut] =
tradeType === TradeType.EXACT_INPUT
? [amountSpecified?.currency, otherCurrency]
: [otherCurrency, amountSpecified?.currency]
// If the trade currencies have switched, consider it stale - do not return the last trade.
const isStale =
(currencyIn && !lastTrade?.inputAmount?.currency.equals(currencyIn)) ||
(currencyOut && !lastTrade?.outputAmount?.currency.equals(currencyOut))
if (isStale) return tradeObject
return { state, trade: lastTrade }
}, [amountSpecified?.currency, lastTrade, otherCurrency, tradeObject, tradeType])
}

View File

@@ -1,5 +1,5 @@
import { BigNumber } from '@ethersproject/bignumber'
import { TransactionResponse, Web3Provider } from '@ethersproject/providers'
import { JsonRpcProvider, TransactionResponse } from '@ethersproject/providers'
// eslint-disable-next-line no-restricted-imports
import { t, Trans } from '@lingui/macro'
import { Trade } from '@uniswap/router-sdk'
@@ -40,7 +40,7 @@ interface FailedCall extends SwapCallEstimate {
export default function useSendSwapTransaction(
account: string | null | undefined,
chainId: number | undefined,
library: Web3Provider | undefined,
library: JsonRpcProvider | undefined,
trade: AnyTrade | undefined, // trade to execute, required
swapCalls: SwapCall[]
): { callback: null | (() => Promise<TransactionResponse>) } {

View File

@@ -1,16 +1,15 @@
import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
import { atom } from 'jotai'
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { useAtomValue } from 'jotai/utils'
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
import { useCurrencyBalances } from 'lib/hooks/useCurrencyBalance'
import useSlippage, { DEFAULT_SLIPPAGE, Slippage } from 'lib/hooks/useSlippage'
import useUSDCPriceImpact, { PriceImpact } from 'lib/hooks/useUSDCPriceImpact'
import { Field, swapAtom } from 'lib/state/swap'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { useEffect, useMemo } from 'react'
import { createContext, PropsWithChildren, useContext, useMemo } from 'react'
import { InterfaceTrade, TradeState } from 'state/routing/types'
import useActiveWeb3React from '../useActiveWeb3React'
import useSlippage, { Slippage } from '../useSlippage'
import useUSDCPriceImpact, { PriceImpact } from '../useUSDCPriceImpact'
import { useBestTrade } from './useBestTrade'
import { INVALID_TRADE, useBestTrade } from './useBestTrade'
import useWrapCallback, { WrapType } from './useWrapCallback'
interface SwapField {
@@ -33,7 +32,6 @@ interface SwapInfo {
// from the current swap inputs, compute the best trade and return it.
function useComputeSwapInfo(): SwapInfo {
const { account } = useActiveWeb3React()
const { type: wrapType } = useWrapCallback()
const isWrapping = wrapType === WrapType.WRAP || wrapType === WrapType.UNWRAP
const { independentField, amount, [Field.INPUT]: currencyIn, [Field.OUTPUT]: currencyOut } = useAtomValue(swapAtom)
@@ -43,10 +41,11 @@ function useComputeSwapInfo(): SwapInfo {
() => tryParseCurrencyAmount(amount, (isExactIn ? currencyIn : currencyOut) ?? undefined),
[amount, isExactIn, currencyIn, currencyOut]
)
const hasAmounts = currencyIn && currencyOut && parsedAmount && !isWrapping
const trade = useBestTrade(
isExactIn ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT,
parsedAmount,
(isExactIn ? currencyOut : currencyIn) ?? undefined
hasAmounts ? parsedAmount : undefined,
hasAmounts ? (isExactIn ? currencyOut : currencyIn) : undefined
)
const amountIn = useMemo(
@@ -57,6 +56,8 @@ function useComputeSwapInfo(): SwapInfo {
() => (isWrapping || !isExactIn ? parsedAmount : trade.trade?.outputAmount),
[isExactIn, isWrapping, parsedAmount, trade.trade?.outputAmount]
)
const { account } = useActiveWeb3React()
const [balanceIn, balanceOut] = useCurrencyBalances(
account,
useMemo(() => [currencyIn, currencyOut], [currencyIn, currencyOut])
@@ -101,21 +102,24 @@ function useComputeSwapInfo(): SwapInfo {
)
}
const swapInfoAtom = atom<SwapInfo>({
const DEFAULT_SWAP_INFO: SwapInfo = {
[Field.INPUT]: {},
[Field.OUTPUT]: {},
trade: { state: TradeState.INVALID },
slippage: { auto: true, allowed: new Percent(0) },
})
trade: INVALID_TRADE,
slippage: DEFAULT_SLIPPAGE,
}
export function SwapInfoUpdater() {
const setSwapInfo = useUpdateAtom(swapInfoAtom)
const SwapInfoContext = createContext(DEFAULT_SWAP_INFO)
export function SwapInfoProvider({ children, disabled }: PropsWithChildren<{ disabled?: boolean }>) {
const swapInfo = useComputeSwapInfo()
useEffect(() => setSwapInfo(swapInfo), [swapInfo, setSwapInfo])
return null
if (disabled) {
return <SwapInfoContext.Provider value={DEFAULT_SWAP_INFO}>{children}</SwapInfoContext.Provider>
}
return <SwapInfoContext.Provider value={swapInfo}>{children}</SwapInfoContext.Provider>
}
/** Requires that SwapInfoUpdater be installed in the DOM tree. **/
export default function useSwapInfo(): SwapInfo {
return useAtomValue(swapInfoAtom)
return useContext(SwapInfoContext)
}

View File

@@ -5,9 +5,10 @@ import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
import { useToken } from 'lib/hooks/useCurrency'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import { Field, Swap, swapAtom } from 'lib/state/swap'
import { useCallback, useLayoutEffect, useState } from 'react'
import { useCallback, useRef } from 'react'
import useOnSupportedNetwork from '../useOnSupportedNetwork'
import { useIsTokenListLoaded } from '../useTokenList'
export type DefaultAddress = string | { [chainId: number]: string | 'NATIVE' } | 'NATIVE'
@@ -71,13 +72,10 @@ export default function useSyncTokenDefaults({
updateSwap((swap) => ({ ...swap, ...defaultSwapState }))
}, [defaultInputAmount, defaultInputToken, defaultOutputAmount, defaultOutputToken, updateSwap])
const [previousChainId, setPreviousChainId] = useState(chainId)
useLayoutEffect(() => {
setPreviousChainId(chainId)
}, [chainId])
useLayoutEffect(() => {
if (chainId && chainId !== previousChainId) {
setToDefaults()
}
}, [chainId, previousChainId, setToDefaults])
const lastChainId = useRef<number | undefined>(undefined)
const shouldSync = useIsTokenListLoaded() && chainId && chainId !== lastChainId.current
if (shouldSync) {
setToDefaults()
lastChainId.current = chainId
}
}

View File

@@ -1,71 +1,107 @@
import { getPriorityConnector, initializeConnector, Web3ReactHooks } from '@web3-react/core'
import { ExternalProvider, JsonRpcProvider, Web3Provider } from '@ethersproject/providers'
import { initializeConnector, Web3ReactHooks } from '@web3-react/core'
import { EIP1193 } from '@web3-react/eip1193'
import { EMPTY } from '@web3-react/empty'
import { Actions, Connector, Provider as Eip1193Provider } from '@web3-react/types'
import { Actions, Connector, Provider as Eip1193Provider, Web3ReactStore } from '@web3-react/types'
import { Url } from '@web3-react/url'
import { useAtom, WritableAtom } from 'jotai'
import { useAtomValue } from 'jotai/utils'
import { atomWithDefault, RESET, useUpdateAtom } from 'jotai/utils'
import { PropsWithChildren, useEffect } from 'react'
import { atom } from 'jotai'
import JsonRpcConnector from 'lib/utils/JsonRpcConnector'
import { createContext, PropsWithChildren, useContext, useEffect, useMemo } from 'react'
const [connector, hooks] = initializeConnector(() => EMPTY)
const EMPTY_CONNECTOR: [Connector, Web3ReactHooks] = [connector, hooks]
const urlConnectorAtom = atomWithDefault<[Connector, Web3ReactHooks]>(() => EMPTY_CONNECTOR)
const injectedConnectorAtom = atomWithDefault<[Connector, Web3ReactHooks]>(() => EMPTY_CONNECTOR)
const web3Atom = atomWithDefault<ReturnType<typeof hooks.useWeb3React>>(() => ({
connector: EMPTY_CONNECTOR[0],
library: undefined,
chainId: undefined,
account: undefined,
active: false,
error: undefined,
}))
type Web3ContextType = {
connector: Connector
library?: (JsonRpcProvider & { provider?: ExternalProvider }) | Web3Provider
chainId?: ReturnType<Web3ReactHooks['useChainId']>
accounts?: ReturnType<Web3ReactHooks['useAccounts']>
account?: ReturnType<Web3ReactHooks['useAccount']>
active?: ReturnType<Web3ReactHooks['useIsActive']>
activating?: ReturnType<Web3ReactHooks['useIsActivating']>
error?: ReturnType<Web3ReactHooks['useError']>
ensNames?: ReturnType<Web3ReactHooks['useENSNames']>
ensName?: ReturnType<Web3ReactHooks['useENSName']>
}
const EMPTY_CONNECTOR = initializeConnector(() => EMPTY)
const EMPTY_CONTEXT: Web3ContextType = { connector: EMPTY }
const jsonRpcConnectorAtom = atom<[Connector, Web3ReactHooks, Web3ReactStore]>(EMPTY_CONNECTOR)
const injectedConnectorAtom = atom<[Connector, Web3ReactHooks, Web3ReactStore]>(EMPTY_CONNECTOR)
const Web3Context = createContext(EMPTY_CONTEXT)
export default function useActiveWeb3React() {
return useAtomValue(web3Atom)
return useContext(Web3Context)
}
function useConnector<T extends { new (actions: Actions, initializer: I): Connector }, I>(
connectorAtom: WritableAtom<[Connector, Web3ReactHooks], typeof RESET | [Connector, Web3ReactHooks]>,
connectorAtom: WritableAtom<[Connector, Web3ReactHooks, Web3ReactStore], [Connector, Web3ReactHooks, Web3ReactStore]>,
Connector: T,
initializer: I | undefined
) {
const [connector, setConnector] = useAtom(connectorAtom)
useEffect(() => {
if (initializer) {
const [connector, hooks] = initializeConnector((actions) => new Connector(actions, initializer))
const [connector, hooks, store] = initializeConnector((actions) => new Connector(actions, initializer))
connector.activate()
setConnector([connector, hooks])
setConnector([connector, hooks, store])
} else {
setConnector(RESET)
setConnector(EMPTY_CONNECTOR)
}
}, [Connector, initializer, setConnector])
return connector
}
interface Web3ProviderProps {
provider?: Eip1193Provider
jsonRpcEndpoint?: string
interface ActiveWeb3ProviderProps {
provider?: Eip1193Provider | JsonRpcProvider
jsonRpcEndpoint?: string | JsonRpcProvider
}
export function Web3Provider({ provider, jsonRpcEndpoint, children }: PropsWithChildren<Web3ProviderProps>) {
const injectedConnector = useConnector(injectedConnectorAtom, EIP1193, provider)
const urlConnector = useConnector(urlConnectorAtom, Url, jsonRpcEndpoint)
const priorityConnector = getPriorityConnector(injectedConnector, urlConnector)
const priorityProvider = priorityConnector.usePriorityProvider()
const priorityWeb3React = priorityConnector.usePriorityWeb3React(priorityProvider)
const setWeb3 = useUpdateAtom(web3Atom)
useEffect(() => {
setWeb3(priorityWeb3React)
}, [priorityWeb3React, setWeb3])
export function ActiveWeb3Provider({
provider,
jsonRpcEndpoint,
children,
}: PropsWithChildren<ActiveWeb3ProviderProps>) {
const Injected = useMemo(() => {
if (provider) {
if (JsonRpcProvider.isProvider(provider)) return JsonRpcConnector
if (JsonRpcProvider.isProvider((provider as any).provider)) {
throw new Error('Eip1193Bridge is experimental: pass your ethers Provider directly')
}
}
return EIP1193
}, [provider]) as { new (actions: Actions, initializer: typeof provider): Connector }
const injectedConnector = useConnector(injectedConnectorAtom, Injected, provider)
const JsonRpc = useMemo(() => {
if (JsonRpcProvider.isProvider(jsonRpcEndpoint)) return JsonRpcConnector
return Url
}, [jsonRpcEndpoint]) as { new (actions: Actions, initializer: typeof jsonRpcEndpoint): Connector }
const jsonRpcConnector = useConnector(jsonRpcConnectorAtom, JsonRpc, jsonRpcEndpoint)
const [connector, hooks] = injectedConnector[1].useIsActive()
? injectedConnector
: jsonRpcConnector ?? EMPTY_CONNECTOR
const library = hooks.useProvider()
const accounts = hooks.useAccounts()
const account = hooks.useAccount()
const activating = hooks.useIsActivating()
const active = hooks.useIsActive()
const chainId = hooks.useChainId()
const ensNames = hooks.useENSNames()
const ensName = hooks.useENSName()
const error = hooks.useError()
const web3 = useMemo(() => {
if (connector === EMPTY || !(active || activating)) {
return EMPTY_CONTEXT
}
return { connector, library, chainId, accounts, account, active, activating, error, ensNames, ensName }
}, [account, accounts, activating, active, chainId, connector, ensName, ensNames, error, library])
// Log web3 errors to facilitate debugging.
const error = priorityConnector.usePriorityError()
useEffect(() => {
if (error) {
console.error('web3 error:', error)
}
}, [error])
return <>{children}</>
return <Web3Context.Provider value={web3}>{children}</Web3Context.Provider>
}

View File

@@ -1,70 +0,0 @@
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useDebounce from 'hooks/useDebounce'
import useIsWindowVisible from 'hooks/useIsWindowVisible'
import { atom } from 'jotai'
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
import { useCallback, useEffect, useState } from 'react'
function useBlock() {
const { chainId, library } = useActiveWeb3React()
const windowVisible = useIsWindowVisible()
const [state, setState] = useState<{ chainId?: number; block?: number }>({ chainId })
const onBlock = useCallback(
(block: number) => {
setState((state) => {
if (state.chainId === chainId) {
if (typeof state.block !== 'number') return { chainId, block }
return { chainId, block: Math.max(block, state.block) }
}
return state
})
},
[chainId]
)
useEffect(() => {
if (library && chainId && windowVisible) {
// If chainId hasn't changed, don't clear the block. This prevents re-fetching still valid data.
setState((state) => (state.chainId === chainId ? state : { chainId }))
library
.getBlockNumber()
.then(onBlock)
.catch((error) => {
console.error(`Failed to get block number for chainId ${chainId}`, error)
})
library.on('block', onBlock)
return () => {
library.removeListener('block', onBlock)
}
}
return undefined
}, [chainId, library, onBlock, windowVisible])
const debouncedBlock = useDebounce(state.block, 100)
return state.block ? debouncedBlock : undefined
}
const blockAtom = atom<number | undefined>(undefined)
export function BlockUpdater() {
const setBlock = useUpdateAtom(blockAtom)
const block = useBlock()
useEffect(() => {
setBlock(block)
}, [block, setBlock])
return null
}
/** Requires that BlockUpdater be installed in the DOM tree. */
export default function useBlockNumber(): number | undefined {
const { chainId } = useActiveWeb3React()
const block = useAtomValue(blockAtom)
return chainId ? block : undefined
}
export function useFastForwardBlockNumber(): (block: number) => void {
return useUpdateAtom(blockAtom)
}

View File

@@ -0,0 +1,78 @@
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useIsWindowVisible from 'hooks/useIsWindowVisible'
import { createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useState } from 'react'
const MISSING_PROVIDER = Symbol()
const BlockNumberContext = createContext<
| {
value?: number
fastForward(block: number): void
}
| typeof MISSING_PROVIDER
>(MISSING_PROVIDER)
function useBlockNumberContext() {
const blockNumber = useContext(BlockNumberContext)
if (blockNumber === MISSING_PROVIDER) {
throw new Error('BlockNumber hooks must be wrapped in a <BlockNumberProvider>')
}
return blockNumber
}
/** Requires that BlockUpdater be installed in the DOM tree. */
export default function useBlockNumber(): number | undefined {
return useBlockNumberContext().value
}
export function useFastForwardBlockNumber(): (block: number) => void {
return useBlockNumberContext().fastForward
}
export function BlockNumberProvider({ children }: { children: ReactNode }) {
const { chainId: activeChainId, library } = useActiveWeb3React()
const [{ chainId, block }, setChainBlock] = useState<{ chainId?: number; block?: number }>({ chainId: activeChainId })
const onBlock = useCallback(
(block: number) => {
setChainBlock((chainBlock) => {
if (chainBlock.chainId === activeChainId) {
if (!chainBlock.block || chainBlock.block < block) {
return { chainId: activeChainId, block }
}
}
return chainBlock
})
},
[activeChainId, setChainBlock]
)
const windowVisible = useIsWindowVisible()
useEffect(() => {
if (library && activeChainId && windowVisible) {
// If chainId hasn't changed, don't clear the block. This prevents re-fetching still valid data.
setChainBlock((chainBlock) => (chainBlock.chainId === activeChainId ? chainBlock : { chainId: activeChainId }))
library
.getBlockNumber()
.then(onBlock)
.catch((error) => {
console.error(`Failed to get block number for chainId ${activeChainId}`, error)
})
library.on('block', onBlock)
return () => {
library.removeListener('block', onBlock)
}
}
return undefined
}, [activeChainId, library, onBlock, setChainBlock, windowVisible])
const value = useMemo(
() => ({
value: chainId === activeChainId ? block : undefined,
fastForward: (block: number) => setChainBlock({ chainId: activeChainId, block }),
}),
[activeChainId, block, chainId]
)
return <BlockNumberContext.Provider value={value}>{children}</BlockNumberContext.Provider>
}

View File

@@ -47,7 +47,7 @@ export function useNativeCurrencyBalances(uncheckedAddresses?: (string | undefin
}
const ERC20Interface = new Interface(ERC20ABI) as Erc20Interface
const tokenBalancesGasRequirement = { gasRequired: 125_000 }
const tokenBalancesGasRequirement = { gasRequired: 185_000 }
/**
* Returns a map of token addresses to their eventually consistent token balances for a single account.

View File

@@ -1,7 +1,7 @@
import { Currency } from '@uniswap/sdk-core'
import { useTheme } from 'lib/theme'
import Vibrant from 'node-vibrant/lib/bundle.js'
import { useEffect, useLayoutEffect, useState } from 'react'
import { useEffect, useState } from 'react'
import useCurrencyLogoURIs from './useCurrencyLogoURIs'
@@ -57,7 +57,7 @@ export default function useCurrencyColor(token?: Currency) {
const theme = useTheme()
const logoURIs = useCurrencyLogoURIs(token)
useLayoutEffect(() => {
useEffect(() => {
let stale = false
if (theme.tokenColorExtraction && token) {

View File

@@ -1,57 +0,0 @@
import { Provider as EthersProvider } from '@ethersproject/abstract-provider'
import { VoidSigner } from '@ethersproject/abstract-signer'
import { Eip1193Bridge as ExperimentalEip1193Bridge } from '@ethersproject/experimental'
import { JsonRpcProvider, JsonRpcSigner } from '@ethersproject/providers'
import { Provider as Eip1193Provider } from '@web3-react/types'
import { ZERO_ADDRESS } from 'constants/misc'
import { useMemo } from 'react'
const voidSigner = new VoidSigner(ZERO_ADDRESS)
class Eip1193Bridge extends ExperimentalEip1193Bridge {
async send(method: string, params?: Array<any>): Promise<any> {
switch (method) {
case 'eth_chainId': {
// TODO(https://github.com/ethers-io/ethers.js/pull/2711): Returns eth_chainId as a hexadecimal.
const result = await this.provider.getNetwork()
return '0x' + result.chainId.toString(16)
}
case 'eth_sendTransaction': {
if (!this.signer) break
// TODO(zzmp): JsonRpcProvider filters from/gas fields from the params.
const req = JsonRpcProvider.hexlifyTransaction(params?.[0], { from: true, gas: true })
const tx = await this.signer.sendTransaction(req)
return tx.hash
}
default:
return super.send(method, params)
}
}
}
interface EthersSigningProvider extends EthersProvider {
getSigner(): JsonRpcSigner
}
export default function useEip1193Provider(
provider?: Eip1193Provider | EthersSigningProvider | EthersProvider
): Eip1193Provider | undefined {
return useMemo(() => {
if (provider) {
if (EthersProvider.isProvider(provider)) {
const signer = 'getSigner' in provider ? provider.getSigner() : null ?? voidSigner
return new Eip1193Bridge(signer, provider)
} else if (EthersProvider.isProvider((provider as ExperimentalEip1193Bridge).provider)) {
/*
* Direct users to use our own wrapper to avoid any pitfalls:
* - Eip1193Bridge is experimental
* - signer is not straightforward
* - bugs out if chainId>8
*/
throw new Error('Eip1193Bridge is experimental: pass your ethers Provider directly')
}
}
return provider
}, [provider])
}

View File

@@ -5,7 +5,7 @@ import useActiveWeb3React from './useActiveWeb3React'
function useOnSupportedNetwork() {
const { chainId } = useActiveWeb3React()
return useMemo(() => chainId && ALL_SUPPORTED_CHAIN_IDS.includes(chainId), [chainId])
return useMemo(() => Boolean(chainId && ALL_SUPPORTED_CHAIN_IDS.includes(chainId)), [chainId])
}
export default useOnSupportedNetwork

View File

@@ -9,7 +9,7 @@ interface PollingOptions<T> {
debounce?: boolean
// If stale, any cached result will be returned, and a new fetch will be initiated.
staleCallback?: (value: T) => boolean
isStale?: (value: T) => boolean
pollingInterval?: number
keepUnusedDataFor?: number
@@ -25,7 +25,7 @@ export default function usePoll<T>(
key = '',
{
debounce = false,
staleCallback,
isStale,
pollingInterval = DEFAULT_POLLING_INTERVAL,
keepUnusedDataFor = DEFAULT_KEEP_UNUSED_DATA_FOR,
}: PollingOptions<T>
@@ -39,11 +39,10 @@ export default function usePoll<T>(
let timeout: number
const entry = cache.get(key)
const isStale = staleCallback && entry?.result !== undefined ? staleCallback(entry.result) : false
if (entry) {
// If there is not a pending fetch (and there should be), queue one.
if (entry.ttl) {
if (isStale) {
if (isStale && entry?.result !== undefined ? isStale(entry.result) : false) {
poll() // stale results should be refetched immediately
} else if (entry.ttl && entry.ttl + keepUnusedDataFor > Date.now()) {
timeout = setTimeout(poll, Math.max(0, entry.ttl - Date.now()))
@@ -57,6 +56,7 @@ export default function usePoll<T>(
return () => {
clearTimeout(timeout)
timeout = 0
}
async function poll(ttl = Date.now() + pollingInterval) {
@@ -66,9 +66,9 @@ export default function usePoll<T>(
// Always set the result in the cache, but only set it as data if the key is still being queried.
const result = await fetch()
cache.set(key, { ttl, result })
setData((data) => (data.key === key ? { key, result } : data))
if (timeout) setData((data) => (data.key === key ? { key, result } : data))
}
}, [cache, debounce, fetch, keepUnusedDataFor, key, pollingInterval, staleCallback])
}, [cache, debounce, fetch, isStale, keepUnusedDataFor, key, pollingInterval])
useEffect(() => {
// Cleanup stale entries when a new key is used.

View File

@@ -1,5 +1,5 @@
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import useAutoSlippageTolerance from 'hooks/useAutoSlippageTolerance'
import useAutoSlippageTolerance, { DEFAULT_AUTO_SLIPPAGE } from 'hooks/useAutoSlippageTolerance'
import { useAtomValue } from 'jotai/utils'
import { autoSlippageAtom, maxSlippageAtom } from 'lib/state/settings'
import { useMemo } from 'react'
@@ -17,6 +17,8 @@ export interface Slippage {
warning?: 'warning' | 'error'
}
export const DEFAULT_SLIPPAGE = { auto: true, allowed: DEFAULT_AUTO_SLIPPAGE }
/** Returns the allowed slippage, and whether it is auto-slippage. */
export default function useSlippage(trade: InterfaceTrade<Currency, Currency, TradeType> | undefined): Slippage {
const shouldUseAutoSlippage = useAtomValue(autoSlippageAtom)
@@ -27,6 +29,9 @@ export default function useSlippage(trade: InterfaceTrade<Currency, Currency, Tr
const auto = shouldUseAutoSlippage || !maxSlippage
const allowed = shouldUseAutoSlippage ? autoSlippage : maxSlippage ?? autoSlippage
const warning = auto ? undefined : getSlippageWarning(allowed)
if (auto && allowed === DEFAULT_AUTO_SLIPPAGE) {
return DEFAULT_SLIPPAGE
}
return { auto, allowed, warning }
}, [autoSlippage, maxSlippage, shouldUseAutoSlippage])
}

View File

@@ -1,10 +1,8 @@
import { NativeCurrency, Token } from '@uniswap/sdk-core'
import { TokenInfo, TokenList } from '@uniswap/token-lists'
import { atom, useAtom } from 'jotai'
import { useAtomValue } from 'jotai/utils'
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
import resolveENSContentHash from 'lib/utils/resolveENSContentHash'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { createContext, PropsWithChildren, useCallback, useContext, useEffect, useMemo, useState } from 'react'
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
import fetchTokenList from './fetchTokenList'
@@ -14,19 +12,61 @@ import { validateTokens } from './validateTokenList'
export const DEFAULT_TOKEN_LIST = 'https://gateway.ipfs.io/ipns/tokens.uniswap.org'
const chainTokenMapAtom = atom<ChainTokenMap | null>(null)
const MISSING_PROVIDER = Symbol()
const ChainTokenMapContext = createContext<ChainTokenMap | undefined | typeof MISSING_PROVIDER>(MISSING_PROVIDER)
export function useIsTokenListLoaded() {
return Boolean(useAtomValue(chainTokenMapAtom))
function useChainTokenMapContext() {
const chainTokenMap = useContext(ChainTokenMapContext)
if (chainTokenMap === MISSING_PROVIDER) {
throw new Error('TokenList hooks must be wrapped in a <TokenListProvider>')
}
return chainTokenMap
}
export function useSyncTokenList(list: string | TokenInfo[] = DEFAULT_TOKEN_LIST): void {
export function useIsTokenListLoaded() {
return Boolean(useChainTokenMapContext())
}
export default function useTokenList(): WrappedTokenInfo[] {
const { chainId } = useActiveWeb3React()
const chainTokenMap = useChainTokenMapContext()
const tokenMap = chainId && chainTokenMap?.[chainId]
return useMemo(() => {
if (!tokenMap) return []
return Object.values(tokenMap).map(({ token }) => token)
}, [tokenMap])
}
export type TokenMap = { [address: string]: Token }
export function useTokenMap(): TokenMap {
const { chainId } = useActiveWeb3React()
const chainTokenMap = useChainTokenMapContext()
const tokenMap = chainId && chainTokenMap?.[chainId]
return useMemo(() => {
if (!tokenMap) return {}
return Object.entries(tokenMap).reduce((map, [address, { token }]) => {
map[address] = token
return map
}, {} as TokenMap)
}, [tokenMap])
}
export function useQueryCurrencies(query = ''): (WrappedTokenInfo | NativeCurrency)[] {
return useQueryTokens(query, useTokenList())
}
export function TokenListProvider({
list = DEFAULT_TOKEN_LIST,
children,
}: PropsWithChildren<{ list?: string | TokenInfo[] }>) {
// Error boundaries will not catch (non-rendering) async errors, but it should still be shown
const [error, setError] = useState<Error>()
if (error) throw error
const [chainTokenMap, setChainTokenMap] = useAtom(chainTokenMapAtom)
useEffect(() => setChainTokenMap(null), [list, setChainTokenMap])
const [chainTokenMap, setChainTokenMap] = useState<ChainTokenMap>()
useEffect(() => setChainTokenMap(undefined), [list])
const { chainId, library } = useActiveWeb3React()
const resolver = useCallback(
@@ -70,34 +110,7 @@ export function useSyncTokenList(list: string | TokenInfo[] = DEFAULT_TOKEN_LIST
}
}
}
}, [chainTokenMap, list, resolver, setChainTokenMap])
}
}, [chainTokenMap, list, resolver])
export default function useTokenList(): WrappedTokenInfo[] {
const { chainId } = useActiveWeb3React()
const chainTokenMap = useAtomValue(chainTokenMapAtom)
const tokenMap = chainId && chainTokenMap?.[chainId]
return useMemo(() => {
if (!tokenMap) return []
return Object.values(tokenMap).map(({ token }) => token)
}, [tokenMap])
}
export type TokenMap = { [address: string]: Token }
export function useTokenMap(): TokenMap {
const { chainId } = useActiveWeb3React()
const chainTokenMap = useAtomValue(chainTokenMapAtom)
const tokenMap = chainId && chainTokenMap?.[chainId]
return useMemo(() => {
if (!tokenMap) return {}
return Object.entries(tokenMap).reduce((map, [address, { token }]) => {
map[address] = token
return map
}, {} as TokenMap)
}, [tokenMap])
}
export function useQueryCurrencies(query = ''): (WrappedTokenInfo | NativeCurrency)[] {
return useQueryTokens(query, useTokenList())
return <ChainTokenMapContext.Provider value={chainTokenMap}>{children}</ChainTokenMapContext.Provider>
}

View File

@@ -1,6 +1,6 @@
import { i18n } from '@lingui/core'
import { I18nProvider } from '@lingui/react'
import { DEFAULT_CATALOG, DEFAULT_LOCALE, SupportedLocale } from 'constants/locales'
import { DEFAULT_LOCALE, SupportedLocale } from 'constants/locales'
import {
af,
ar,
@@ -78,12 +78,11 @@ const plurals: LocalePlural = {
export async function dynamicActivate(locale: SupportedLocale) {
i18n.loadLocaleData(locale, { plurals: () => plurals[locale] })
// There are no default messages in production; instead, bundle the default to save a network request:
// see https://github.com/lingui/js-lingui/issues/388#issuecomment-497779030
const catalog =
locale === DEFAULT_LOCALE ? DEFAULT_CATALOG : await import(`${process.env.REACT_APP_LOCALES}/${locale}.js`)
// Bundlers will either export it as default or as a named export named default.
i18n.load(locale, catalog.messages || catalog.default.messages)
try {
const catalog = await import(`${process.env.REACT_APP_LOCALES}/${locale}.js`)
// Bundlers will either export it as default or as a named export named default.
i18n.load(locale, catalog.messages || catalog.default.messages)
} catch {}
i18n.activate(locale)
}
@@ -103,6 +102,16 @@ export function Provider({ locale, forceRenderAfterLocaleChange = true, onActiva
})
}, [locale, onActivate])
// Initialize the locale immediately if it is DEFAULT_LOCALE, so that keys are shown while the translation messages load.
// This renders the translation _keys_, not the translation _messages_, which is only acceptable while loading the DEFAULT_LOCALE,
// as [there are no "default" messages](https://github.com/lingui/js-lingui/issues/388#issuecomment-497779030).
// See https://github.com/lingui/js-lingui/issues/1194#issuecomment-1068488619.
if (i18n.locale === undefined && locale === DEFAULT_LOCALE) {
i18n.loadLocaleData(DEFAULT_LOCALE, { plurals: () => plurals[DEFAULT_LOCALE] })
i18n.load(DEFAULT_LOCALE, {})
i18n.activate(DEFAULT_LOCALE)
}
return (
<I18nProvider forceRenderOnLocaleChange={forceRenderAfterLocaleChange} i18n={i18n}>
{children}

View File

@@ -11,8 +11,8 @@ export const store = createStore(reducer)
export default multicall
export function MulticallUpdater() {
const latestBlockNumber = useBlockNumber()
const { chainId } = useActiveWeb3React()
const latestBlockNumber = useBlockNumber()
const contract = useInterfaceMulticall()
return <multicall.Updater chainId={chainId} latestBlockNumber={latestBlockNumber} contract={contract} />
}

View File

@@ -0,0 +1,39 @@
import { JsonRpcProvider } from '@ethersproject/providers'
import { Actions, Connector, ProviderConnectInfo, ProviderRpcError } from '@web3-react/types'
function parseChainId(chainId: string) {
return Number.parseInt(chainId, 16)
}
export default class JsonRpcConnector extends Connector {
constructor(actions: Actions, public customProvider: JsonRpcProvider) {
super(actions)
customProvider
.on('connect', ({ chainId }: ProviderConnectInfo): void => {
this.actions.update({ chainId: parseChainId(chainId) })
})
.on('disconnect', (error: ProviderRpcError): void => {
this.actions.reportError(error)
})
.on('chainChanged', (chainId: string): void => {
this.actions.update({ chainId: parseChainId(chainId) })
})
.on('accountsChanged', (accounts: string[]): void => {
this.actions.update({ accounts })
})
}
async activate() {
this.actions.startActivation()
try {
const [{ chainId }, accounts] = await Promise.all([
this.customProvider.getNetwork(),
this.customProvider.listAccounts(),
])
this.actions.update({ chainId, accounts })
} catch (e) {
this.actions.reportError(e)
}
}
}

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: uniswap-interface\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2022-03-23 19:09\n"
"PO-Revision-Date: 2022-03-24 20:06\n"
"Last-Translator: \n"
"Language: af_ZA\n"
"Language-Team: Afrikaans\n"
@@ -798,6 +798,10 @@ msgstr "Kon nie koppel nie. Probeer om die bladsy te verfris."
msgid "Error details"
msgstr "Foutbesonderhede"
#: src/lib/components/Swap/Toolbar/Caption.tsx
msgid "Error fetching trade"
msgstr "Kon nie handel haal nie"
#: src/components/SearchModal/ManageLists.tsx
msgid "Error importing list"
msgstr "Kon nie lys invoer nie"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: uniswap-interface\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2022-03-23 19:09\n"
"PO-Revision-Date: 2022-03-24 20:06\n"
"Last-Translator: \n"
"Language: ar_SA\n"
"Language-Team: Arabic\n"
@@ -798,6 +798,10 @@ msgstr "خطأ في الاتصال. حاول تحديث الصفحة."
msgid "Error details"
msgstr "تفاصيل الخطأ"
#: src/lib/components/Swap/Toolbar/Caption.tsx
msgid "Error fetching trade"
msgstr "خطأ في جلب التجارة"
#: src/components/SearchModal/ManageLists.tsx
msgid "Error importing list"
msgstr "خطأ في استيراد قائمة"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: uniswap-interface\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2022-03-23 19:09\n"
"PO-Revision-Date: 2022-03-24 20:06\n"
"Last-Translator: \n"
"Language: ca_ES\n"
"Language-Team: Catalan\n"
@@ -798,6 +798,10 @@ msgstr "Error de connexió. Proveu d'actualitzar la pàgina."
msgid "Error details"
msgstr "Detalls de l'error"
#: src/lib/components/Swap/Toolbar/Caption.tsx
msgid "Error fetching trade"
msgstr "S'ha produït un error en recuperar el comerç"
#: src/components/SearchModal/ManageLists.tsx
msgid "Error importing list"
msgstr "Error en importar la llista"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: uniswap-interface\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2022-03-23 19:09\n"
"PO-Revision-Date: 2022-03-24 20:06\n"
"Last-Translator: \n"
"Language: cs_CZ\n"
"Language-Team: Czech\n"
@@ -798,6 +798,10 @@ msgstr "Chyba připojení. Zkuste obnovit stránku."
msgid "Error details"
msgstr "Detaily chyby"
#: src/lib/components/Swap/Toolbar/Caption.tsx
msgid "Error fetching trade"
msgstr "Chyba při načítání obchodu"
#: src/components/SearchModal/ManageLists.tsx
msgid "Error importing list"
msgstr "Chyba importu seznamu"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: uniswap-interface\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2022-03-23 19:09\n"
"PO-Revision-Date: 2022-03-24 20:06\n"
"Last-Translator: \n"
"Language: da_DK\n"
"Language-Team: Danish\n"
@@ -798,6 +798,10 @@ msgstr "Der opstod en fejl. Prøv at opdatere siden."
msgid "Error details"
msgstr "Fejldetaljer"
#: src/lib/components/Swap/Toolbar/Caption.tsx
msgid "Error fetching trade"
msgstr "Fejl ved hentning af handel"
#: src/components/SearchModal/ManageLists.tsx
msgid "Error importing list"
msgstr "Fejl ved import af liste"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: uniswap-interface\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2022-03-23 19:09\n"
"PO-Revision-Date: 2022-03-24 20:06\n"
"Last-Translator: \n"
"Language: de_DE\n"
"Language-Team: German\n"
@@ -798,6 +798,10 @@ msgstr "Verbindungsfehler. Versuchen Sie die Seite neu zu laden."
msgid "Error details"
msgstr "Fehlerdetails"
#: src/lib/components/Swap/Toolbar/Caption.tsx
msgid "Error fetching trade"
msgstr "Fehler beim Abrufen des Handels"
#: src/components/SearchModal/ManageLists.tsx
msgid "Error importing list"
msgstr "Fehler beim Import der Liste"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: uniswap-interface\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2022-03-23 19:09\n"
"PO-Revision-Date: 2022-03-24 20:06\n"
"Last-Translator: \n"
"Language: el_GR\n"
"Language-Team: Greek\n"
@@ -798,6 +798,10 @@ msgstr "Σφάλμα σύνδεσης. Προσπαθήστε ξανά αναν
msgid "Error details"
msgstr "Λεπτομέρειες σφάλματος"
#: src/lib/components/Swap/Toolbar/Caption.tsx
msgid "Error fetching trade"
msgstr "Σφάλμα κατά την ανάκτηση της συναλλαγής"
#: src/components/SearchModal/ManageLists.tsx
msgid "Error importing list"
msgstr "Σφάλμα εισαγωγής λίστας"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: uniswap-interface\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2022-03-23 19:09\n"
"PO-Revision-Date: 2022-03-24 20:06\n"
"Last-Translator: \n"
"Language: es_ES\n"
"Language-Team: Spanish\n"
@@ -798,6 +798,10 @@ msgstr "Error de conexión. Intente actualizar la página."
msgid "Error details"
msgstr "Error de detalles"
#: src/lib/components/Swap/Toolbar/Caption.tsx
msgid "Error fetching trade"
msgstr "Error al obtener comercio"
#: src/components/SearchModal/ManageLists.tsx
msgid "Error importing list"
msgstr "Error importando lista"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: uniswap-interface\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2022-03-23 19:09\n"
"PO-Revision-Date: 2022-03-24 20:06\n"
"Last-Translator: \n"
"Language: fi_FI\n"
"Language-Team: Finnish\n"
@@ -798,6 +798,10 @@ msgstr "Virhe yhdistettäessä. Yritä päivittää sivu."
msgid "Error details"
msgstr "Virheen tiedot"
#: src/lib/components/Swap/Toolbar/Caption.tsx
msgid "Error fetching trade"
msgstr "Virhe kauppaa haettaessa"
#: src/components/SearchModal/ManageLists.tsx
msgid "Error importing list"
msgstr "Virhe tuotaessa luetteloa"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: uniswap-interface\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2022-03-23 19:09\n"
"PO-Revision-Date: 2022-03-24 20:06\n"
"Last-Translator: \n"
"Language: fr_FR\n"
"Language-Team: French\n"
@@ -798,6 +798,10 @@ msgstr "Erreur de connexion. Essayez d'actualiser la page."
msgid "Error details"
msgstr "Détails de l'erreur"
#: src/lib/components/Swap/Toolbar/Caption.tsx
msgid "Error fetching trade"
msgstr "Erreur lors de la récupération de l'échange"
#: src/components/SearchModal/ManageLists.tsx
msgid "Error importing list"
msgstr "Erreur lors de l'importation de la liste"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: uniswap-interface\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2022-03-23 19:09\n"
"PO-Revision-Date: 2022-03-24 20:06\n"
"Last-Translator: \n"
"Language: he_IL\n"
"Language-Team: Hebrew\n"
@@ -798,6 +798,10 @@ msgstr "שגיאה בהתחברות. נסה לרענן את הדף."
msgid "Error details"
msgstr "פרטי שגיאה"
#: src/lib/components/Swap/Toolbar/Caption.tsx
msgid "Error fetching trade"
msgstr "שגיאה בשליפת המסחר"
#: src/components/SearchModal/ManageLists.tsx
msgid "Error importing list"
msgstr "שגיאה בייבוא הרשימה"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: uniswap-interface\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2022-03-23 19:09\n"
"PO-Revision-Date: 2022-03-24 20:06\n"
"Last-Translator: \n"
"Language: hu_HU\n"
"Language-Team: Hungarian\n"
@@ -798,6 +798,10 @@ msgstr "Hiba történt a csatlakozáskor. Próbálja frissíteni az oldalt."
msgid "Error details"
msgstr "Hiba részletei"
#: src/lib/components/Swap/Toolbar/Caption.tsx
msgid "Error fetching trade"
msgstr "Hiba történt a kereskedés lekérésekor"
#: src/components/SearchModal/ManageLists.tsx
msgid "Error importing list"
msgstr "Hiba történt a lista importálásakor"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: uniswap-interface\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2022-03-23 19:09\n"
"PO-Revision-Date: 2022-03-24 20:06\n"
"Last-Translator: \n"
"Language: id_ID\n"
"Language-Team: Indonesian\n"
@@ -798,6 +798,10 @@ msgstr "Terjadi kesalahan saat menyambungkan. Coba muat ulang halaman."
msgid "Error details"
msgstr "Rincian kesalahan"
#: src/lib/components/Swap/Toolbar/Caption.tsx
msgid "Error fetching trade"
msgstr "Terjadi kesalahan saat mengambil perdagangan"
#: src/components/SearchModal/ManageLists.tsx
msgid "Error importing list"
msgstr "Terjadi kesalahan saat mengimpor daftar"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: uniswap-interface\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2022-03-23 19:09\n"
"PO-Revision-Date: 2022-03-24 20:06\n"
"Last-Translator: \n"
"Language: it_IT\n"
"Language-Team: Italian\n"
@@ -798,6 +798,10 @@ msgstr "Errore di connessione. Prova ad aggiornare la pagina."
msgid "Error details"
msgstr "Dettagli circa l'errore"
#: src/lib/components/Swap/Toolbar/Caption.tsx
msgid "Error fetching trade"
msgstr "Errore durante il recupero dell'operazione"
#: src/components/SearchModal/ManageLists.tsx
msgid "Error importing list"
msgstr "Errore nell'importazione della lista"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: uniswap-interface\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2022-03-23 19:09\n"
"PO-Revision-Date: 2022-03-29 06:07\n"
"Last-Translator: \n"
"Language: ja_JP\n"
"Language-Team: Japanese\n"
@@ -206,7 +206,7 @@ msgstr "価格への大きな影響がある取引を許可し、確認画面を
#: src/lib/components/Swap/SwapButton/useApprovalData.tsx
msgid "Allow in your wallet"
msgstr "あなたの財布に入れてください"
msgstr "ウォレットで許可する"
#: src/pages/Swap/index.tsx
msgid "Allow the Uniswap Protocol to use your {0}"
@@ -341,7 +341,7 @@ msgstr "非常に安定したペアに最適"
#: src/components/swap/SwapRoute.tsx
msgid "Best price route costs ~{formattedGasPriceString} in gas."
msgstr "ベストプライスルートコスト〜{formattedGasPriceString} ガスインチ"
msgstr "ベスト価格のルートでガス代として〜{formattedGasPriceString} の費用がかかります。"
#: src/components/Blocklist/index.tsx
msgid "Blocked address"
@@ -565,7 +565,7 @@ msgstr "ウォレットに接続して流動性を確認します。"
#: src/lib/components/Swap/Toolbar/Caption.tsx
msgid "Connect wallet to swap"
msgstr "ウォレットをスワップに接続します"
msgstr "ウォレットを接続してスワップする"
#: src/lib/components/Wallet.tsx
msgid "Connect your wallet"
@@ -676,7 +676,7 @@ msgstr "流動性を預ける"
#: src/components/NetworkAlert/NetworkAlert.tsx
msgid "Deposit tokens to the {label} network."
msgstr "{label} ネットワークにトークンをデポジットします"
msgstr "{label} ネットワークにトークンを預け入れます"
#: src/pages/Earn/index.tsx
msgid "Deposit your Liquidity Provider tokens to receive UNI, the Uniswap protocol governance token."
@@ -777,7 +777,7 @@ msgstr "有効なトークンアドレスを入力してください"
#: src/hooks/useWrapCallback.tsx
#: src/hooks/useWrapCallback.tsx
msgid "Enter {0} amount"
msgstr "{0} 金額を入力してください"
msgstr "{0} の数量を入力してください"
#: src/components/TransactionConfirmationModal/index.tsx
#: src/components/TransactionConfirmationModal/index.tsx
@@ -798,13 +798,17 @@ msgstr "接続中にエラーが発生しました。ページを更新してく
msgid "Error details"
msgstr "エラーの詳細"
#: src/lib/components/Swap/Toolbar/Caption.tsx
msgid "Error fetching trade"
msgstr "取引の取得中にエラーが発生しました"
#: src/components/SearchModal/ManageLists.tsx
msgid "Error importing list"
msgstr "リストのインポートエラー"
#: src/components/swap/GasEstimateBadge.tsx
msgid "Estimate may differ due to your wallet gas settings"
msgstr "ウォレットガス設定により、見積もりが異なる場合があります"
msgstr "ウォレットガス設定により、見積もりが異なる場合があります"
#: src/components/swap/GasEstimateBadge.tsx
msgid "Estimated network fee"
@@ -824,7 +828,7 @@ msgstr "あなたが利用していないトークンリストからの検索結
#: src/components/swap/AdvancedSwapDetails.tsx
msgid "Expected Output"
msgstr "期待される出力"
msgstr "予想される結果"
#: src/components/Settings/index.tsx
msgid "Expert Mode"
@@ -844,7 +848,7 @@ msgstr "Uniswap Analyticsをご覧ください。"
#: src/components/Popups/FailedNetworkSwitchPopup.tsx
msgid "Failed to switch networks from the Uniswap Interface. In order to use Uniswap on {0}, you must change the network in your wallet."
msgstr "ユニスワップインターフェイスからのネットワークの切り替えに失敗しました。 {0}でユニスワップを使用するには、ウォレットのネットワークを変更する必要があります。"
msgstr "Uniswapのインターフェイスからのネットワークの切り替えに失敗しました。 {0} でUniswapを使用するには、ウォレットのネットワークを変更する必要があります。"
#: src/components/PositionPreview/index.tsx
msgid "Fee Tier"
@@ -856,11 +860,11 @@ msgstr "手数料レベル"
#: src/components/swap/SwapDetailsDropdown.tsx
msgid "Fetching best price..."
msgstr "最高の価格を取得しています..."
msgstr "ベストな価格を取得中…"
#: src/lib/components/Swap/Toolbar/Caption.tsx
msgid "Fetching best price…"
msgstr "最安値を取得…"
msgstr "ベストな価格を取得…"
#: src/lib/components/Swap/Output.tsx
#: src/pages/Vote/VotePage.tsx
@@ -988,7 +992,7 @@ msgstr "流動性が不足しているため、取引できません。"
#: src/lib/components/Swap/Toolbar/Caption.tsx
msgid "Insufficient liquidity in the pool for your trade"
msgstr "あなたの取引のためのプールの不十分な流動性"
msgstr "流動性が不足しているため、取引できません。"
#: src/hooks/useWrapCallback.tsx
#: src/hooks/useWrapCallback.tsx
@@ -1324,7 +1328,7 @@ msgstr "取引結果は概算です。<0>{0} {1}</0> 以上を買えない場合
#: src/lib/components/Swap/Summary/index.tsx
msgid "Output is estimated. You will receive at least {0} {1} or the transaction will revert."
msgstr "出力は推定されます。少なくとも {1} を受け取るか、トランザクションが元に戻り {0}。"
msgstr "取引結果は概算です。{0} {1} 以上を買えない場合は、取引は差し戻されます。"
#: src/lib/components/Swap/Summary/index.tsx
msgid "Output is estimated. You will send at most {0} {1} or the transaction will revert."
@@ -1413,7 +1417,7 @@ msgstr "プールの概要"
#: src/lib/components/BrandedFooter.tsx
msgid "Powered by the Uniswap protocol"
msgstr "ユニスワッププロトコルを搭載"
msgstr "Uniswapプロトコルによって提供"
#: src/pages/AddLiquidity/index.tsx
msgid "Preview"

Some files were not shown because too many files have changed in this diff Show More