Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71aebf33db | ||
|
|
5ff428b04b | ||
|
|
acb0c2056e | ||
|
|
0a4bcb62da | ||
|
|
f50bcbdb2d | ||
|
|
cbe421ee23 | ||
|
|
3439786c38 | ||
|
|
6294915be6 | ||
|
|
984c742d0e | ||
|
|
00b151d7fa | ||
|
|
5967cf5d9d | ||
|
|
e480f0ebe5 | ||
|
|
f6ceecbc5e | ||
|
|
a0348b45be | ||
|
|
e4b37cffcc | ||
|
|
dd69cccf91 | ||
|
|
8b228de88f | ||
|
|
f91fc3c6a6 | ||
|
|
e0e2b40f9f | ||
|
|
bc1c61b63a | ||
|
|
446ad3e0d4 | ||
|
|
65e58a08cf | ||
|
|
71b20b432c | ||
|
|
ecfa179b3f | ||
|
|
6c94a0f585 | ||
|
|
600aeaaff1 | ||
|
|
3bfbc74e47 | ||
|
|
84f76e34b2 | ||
|
|
b965bed865 | ||
|
|
a9039e8d0b | ||
|
|
60d35b46f3 | ||
|
|
3d422cf707 | ||
|
|
84c70ac84d | ||
|
|
de3a33dfcb | ||
|
|
99a084f230 | ||
|
|
e880955743 | ||
|
|
bbf43fcd27 | ||
|
|
a00ac56389 | ||
|
|
7201944bc2 | ||
|
|
b0ff0f83b0 | ||
|
|
5cf9e84db5 | ||
|
|
c0bdb8db12 | ||
|
|
2d8f767d74 | ||
|
|
1303416eca | ||
|
|
124f6420a5 | ||
|
|
91f5fc0881 | ||
|
|
ec831f8433 | ||
|
|
56bd9b68d7 | ||
|
|
865d21f039 | ||
|
|
dceadf8472 | ||
|
|
cd3a91bca8 | ||
|
|
de1f5d1adc | ||
|
|
b5d403768f | ||
|
|
c4c811aeb3 | ||
|
|
33c24a3f05 | ||
|
|
9ef2b3a116 | ||
|
|
afe38a2d10 | ||
|
|
d28607a1c8 | ||
|
|
7fb363ac46 |
@@ -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
45
INTERFACE_README.md
Normal 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).
|
||||
56
README.md
56
README.md
@@ -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).
|
||||
|
||||
[](https://github.com/Uniswap/uniswap-interface/actions/workflows/unit-tests.yaml)
|
||||
[](https://github.com/Uniswap/uniswap-interface/actions/workflows/integration-tests.yaml)
|
||||
[](https://github.com/Uniswap/uniswap-interface/actions/workflows/lint.yml)
|
||||
[](https://github.com/Uniswap/uniswap-interface/actions/workflows/release.yaml)
|
||||
# Uniswap Labs Interface
|
||||
|
||||
[](https://github.com/Uniswap/interface/actions/workflows/unit-tests.yaml)
|
||||
[](https://github.com/Uniswap/interface/actions/workflows/integration-tests.yaml)
|
||||
[](https://github.com/Uniswap/interface/actions/workflows/lint.yml)
|
||||
[](https://github.com/Uniswap/interface/actions/workflows/release.yaml)
|
||||
[](https://crowdin.com/project/uniswap-interface)
|
||||
|
||||
An open source interface for Uniswap -- a protocol for decentralized exchange of Ethereum tokens.
|
||||
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
40
WIDGETS_README.md
Normal 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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
40
package.json
40
package.json
@@ -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",
|
||||
|
||||
@@ -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: [
|
||||
|
||||
BIN
src/assets/images/widget-screenshot.png
Normal file
BIN
src/assets/images/widget-screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 106 KiB |
@@ -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>
|
||||
|
||||
@@ -33,6 +33,7 @@ const BLOCKED_ADDRESSES: string[] = [
|
||||
'0x5512d943ed1f7c8a43f3435c85f7ab68b30121b0',
|
||||
'0xc455f7fd3e0e12afd51fba5c106909934d8a0e4a',
|
||||
'0x629e7Da20197a5429d30da36E77d06CdF796b71A',
|
||||
'0x7FF9cFad3877F21d41Da833E2F775dB0569eE3D9',
|
||||
]
|
||||
|
||||
export default function Blocklist({ children }: { children: ReactNode }) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 } })
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
`
|
||||
|
||||
|
||||
@@ -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}</>
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -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
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)};
|
||||
|
||||
@@ -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>} />
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
])
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
@@ -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 }>`
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
`
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
@@ -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>) } {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
78
src/lib/hooks/useBlockNumber.tsx
Normal file
78
src/lib/hooks/useBlockNumber.tsx
Normal 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>
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
@@ -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} />
|
||||
}
|
||||
|
||||
39
src/lib/utils/JsonRpcConnector.ts
Normal file
39
src/lib/utils/JsonRpcConnector.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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 "خطأ في استيراد قائمة"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 "Σφάλμα εισαγωγής λίστας"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 "שגיאה בייבוא הרשימה"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user