Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b08bb7eaff | ||
|
|
3a36ac5538 | ||
|
|
2962cd0e14 | ||
|
|
6a311aa6d7 | ||
|
|
e78b6d61f2 | ||
|
|
365b429c0b | ||
|
|
32d300009e | ||
|
|
806623c602 | ||
|
|
3272f8e9db | ||
|
|
010ef108eb | ||
|
|
19b1e9e399 | ||
|
|
6287b95b92 | ||
|
|
4e8a6e2a4c | ||
|
|
848c7b418b | ||
|
|
f619cf4353 | ||
|
|
877db71e2a | ||
|
|
f4b5727fdb | ||
|
|
1fd6b1e659 | ||
|
|
6570beef32 | ||
|
|
b57f58ab35 | ||
|
|
2f40c4f614 | ||
|
|
3f9c34d37d | ||
|
|
1d5c6530e3 | ||
|
|
78f294c340 | ||
|
|
90d24a26f3 | ||
|
|
7a3a5bd546 | ||
|
|
081ae15aa8 | ||
|
|
f5a5c5e70d | ||
|
|
e05e0206b7 | ||
|
|
344b4340ae | ||
|
|
eeef306bdd | ||
|
|
63a491d4b1 | ||
|
|
6831a73fdf | ||
|
|
a4aef02747 | ||
|
|
c26716047f | ||
|
|
0fa238af0b | ||
|
|
21c1484c0e | ||
|
|
8a845ee0e9 | ||
|
|
f5229ca838 | ||
|
|
875203f0ef | ||
|
|
91a8202737 | ||
|
|
0b4819d165 | ||
|
|
e7d3289754 | ||
|
|
0698e0f82a | ||
|
|
0350cc4701 | ||
|
|
997052869d | ||
|
|
9ec16c2ba8 | ||
|
|
e2cf8f1642 | ||
|
|
ed6952d1f7 | ||
|
|
3277d70e93 | ||
|
|
d1a31fe763 | ||
|
|
f88af029ae | ||
|
|
9f3e49b4d8 | ||
|
|
d4911d1054 | ||
|
|
90df9c4ced | ||
|
|
14f15d1fd6 |
2
.env
2
.env
@@ -1,2 +1,2 @@
|
||||
REACT_APP_CHAIN_ID="1"
|
||||
REACT_APP_NETWORK_URL="https://mainnet.infura.io/v3/acb7e55995d04c49bfb52b7141599467"
|
||||
REACT_APP_NETWORK_URL="https://mainnet.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847"
|
||||
@@ -1,5 +1,5 @@
|
||||
REACT_APP_CHAIN_ID="1"
|
||||
REACT_APP_NETWORK_URL="https://mainnet.infura.io/v3/febcb10ca2754433a61e0805bc6c047d"
|
||||
REACT_APP_NETWORK_URL="https://mainnet.infura.io/v3/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"
|
||||
|
||||
27
.github/ISSUE_TEMPLATE/token-request.md
vendored
27
.github/ISSUE_TEMPLATE/token-request.md
vendored
@@ -1,27 +0,0 @@
|
||||
---
|
||||
name: Token Request
|
||||
about: Request a token addition
|
||||
title: ''
|
||||
labels: token request
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Please provide the following information for your token.**
|
||||
|
||||
Token Address:
|
||||
Token Name (from contract):
|
||||
Token Decimals (from contract):
|
||||
Token Symbol (from contract):
|
||||
Uniswap Exchange Address of Token:
|
||||
|
||||
Link to the official homepage of token:
|
||||
Link to CoinMarketCap or CoinGecko page of token:
|
||||
|
||||
Some tokens (e.g. BNB) do not work with Uniswap v1. In order to assess if your token works correctly, please complete small-value transactions of each of the types below, and submit the Etherscan transaction links for our review.
|
||||
Test `addLiquidity` transaction:
|
||||
Test `swap` transaction:
|
||||
Test `removeLiquidity` transaction:
|
||||
|
||||
Are you willing to add liquidity to the liquidity pool for this token? (Y/N):
|
||||
If so, how much liquidity are you willing to add?:
|
||||
17
.github/workflows/release.yaml
vendored
17
.github/workflows/release.yaml
vendored
@@ -4,13 +4,8 @@ on:
|
||||
schedule:
|
||||
- cron: '0 12 * * 1-4'
|
||||
|
||||
# releases are triggered on changes to this file
|
||||
push:
|
||||
branches:
|
||||
- v2
|
||||
paths:
|
||||
- '.github/workflows/release.yaml'
|
||||
- '.env.production'
|
||||
# manual trigger
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
bump_version:
|
||||
@@ -44,14 +39,14 @@ jobs:
|
||||
node-version: '12'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --ignore-scripts --frozen-lockfile
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Build the IPFS bundle
|
||||
run: yarn ipfs-build
|
||||
run: yarn build
|
||||
|
||||
- name: Pin to IPFS
|
||||
id: upload
|
||||
uses: anantaramdas/ipfs-pinata-deploy-action@v1.5.2
|
||||
uses: anantaramdas/ipfs-pinata-deploy-action@39bbda1ce1fe24c69c6f57861b8038278d53688d
|
||||
with:
|
||||
pin-name: Uniswap ${{ needs.bump_version.outputs.new_tag }}
|
||||
path: './build'
|
||||
@@ -97,7 +92,7 @@ jobs:
|
||||
|
||||
Preferred URLs:
|
||||
- https://${{ steps.convert_cidv0.outputs.cidv1 }}.ipfs.dweb.link/
|
||||
- https://${{ steps.convert_cidv0.outputs.cidv1 }}.cf-ipfs.com/
|
||||
- https://${{ steps.convert_cidv0.outputs.cidv1 }}.ipfs.cf-ipfs.com/
|
||||
- [ipfs://${{ steps.upload.outputs.hash }}/](ipfs://${{ steps.upload.outputs.hash }}/)
|
||||
|
||||
Other IPFS gateways:
|
||||
|
||||
12
.github/workflows/tests.yaml
vendored
12
.github/workflows/tests.yaml
vendored
@@ -2,10 +2,10 @@ name: Tests
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- v2
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- v2
|
||||
- master
|
||||
jobs:
|
||||
integration-tests:
|
||||
name: Integration tests
|
||||
@@ -26,7 +26,9 @@ jobs:
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- run: yarn install
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn cypress install
|
||||
- run: yarn build
|
||||
- run: yarn integration-test
|
||||
|
||||
unit-tests:
|
||||
@@ -48,7 +50,7 @@ jobs:
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- run: yarn install --ignore-scripts --frozen-lockfile
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn test
|
||||
|
||||
lint:
|
||||
@@ -70,6 +72,6 @@ jobs:
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- run: yarn install --ignore-scripts --frozen-lockfile
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn lint
|
||||
|
||||
|
||||
48
README.md
48
README.md
@@ -1,11 +1,12 @@
|
||||
# Uniswap Frontend
|
||||
# Uniswap Interface
|
||||
|
||||
[](https://github.com/Uniswap/uniswap-frontend/actions?query=workflow%3ATests)
|
||||
[](https://github.com/Uniswap/uniswap-interface/actions?query=workflow%3ATests)
|
||||
[](https://prettier.io/)
|
||||
|
||||
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://uniswap.org/docs/)
|
||||
- Twitter: [@UniswapProtocol](https://twitter.com/UniswapProtocol)
|
||||
- Reddit: [/r/Uniswap](https://www.reddit.com/r/Uniswap/)
|
||||
@@ -13,11 +14,17 @@ An open source interface for Uniswap -- a protocol for decentralized exchange of
|
||||
- Discord: [Uniswap](https://discord.gg/Y7TF6QA)
|
||||
- Whitepaper: [Link](https://hackmd.io/C-DvwDSfSxuh-Gd4WKE_ig)
|
||||
|
||||
## Accessing the frontend
|
||||
## Accessing the Uniswap Interface
|
||||
|
||||
To access the front end, use an IPFS gateway link from the
|
||||
[latest release](https://github.com/Uniswap/uniswap-frontend/releases/latest)
|
||||
or visit [uniswap.exchange](https://uniswap.exchange).
|
||||
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).
|
||||
|
||||
## Listing a token
|
||||
|
||||
Please see the
|
||||
[@uniswap/default-token-list](https://github.com/uniswap/default-token-list)
|
||||
repository.
|
||||
|
||||
## Development
|
||||
|
||||
@@ -27,31 +34,32 @@ or visit [uniswap.exchange](https://uniswap.exchange).
|
||||
yarn
|
||||
```
|
||||
|
||||
### Configure Environment (optional)
|
||||
|
||||
Copy `.env` to `.env.local` and change the appropriate variables.
|
||||
|
||||
### Run
|
||||
|
||||
```bash
|
||||
yarn start
|
||||
```
|
||||
|
||||
To have the frontend default to a different network, make a copy of `.env` named `.env.local`,
|
||||
change `REACT_APP_NETWORK_ID` to `"{yourNetworkId}"`, and change `REACT_APP_NETWORK_URL` to e.g.
|
||||
`"https://{yourNetwork}.infura.io/v3/{yourKey}"`.
|
||||
### Configuring the environment (optional)
|
||||
|
||||
Note that the front end only works properly on testnets where both
|
||||
To have the interface default to a different network when a wallet is not connected:
|
||||
|
||||
1. Make a copy of `.env` named `.env.local`
|
||||
2. Change `REACT_APP_NETWORK_ID` to `"{YOUR_NETWORK_ID}"`
|
||||
3. Change `REACT_APP_NETWORK_URL` to e.g. `"https://{YOUR_NETWORK_ID}.infura.io/v3/{YOUR_INFURA_KEY}"`
|
||||
|
||||
Note that the interface only works on testnets where both
|
||||
[Uniswap V2](https://uniswap.org/docs/v2/smart-contracts/factory/) and
|
||||
[multicall](https://github.com/makerdao/multicall) are deployed.
|
||||
The frontend will not work on other networks.
|
||||
The interface will not work on other networks.
|
||||
|
||||
## Contributions
|
||||
|
||||
**Please open all pull requests against the `v2` branch.**
|
||||
CI checks will run against all PRs.
|
||||
**Please open all pull requests against the `master` branch.**
|
||||
CI checks will run against all PRs.
|
||||
|
||||
## Accessing Uniswap V1 interface
|
||||
## Accessing Uniswap Interface 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-frontend/releases/tag/v1.0.0).
|
||||
The Uniswap Interface supports swapping against, and migrating or removing liquidity from Uniswap V1. However,
|
||||
if you would like to use 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).
|
||||
|
||||
@@ -16,4 +16,35 @@ describe('Add Liquidity', () => {
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'SKL')
|
||||
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'MKR')
|
||||
})
|
||||
|
||||
it('single token can be selected', () => {
|
||||
cy.visit('/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'SKL')
|
||||
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'MKR')
|
||||
})
|
||||
|
||||
it('redirects /add/token-token to add/token/token', () => {
|
||||
cy.visit('/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.url().should(
|
||||
'contain',
|
||||
'/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85'
|
||||
)
|
||||
})
|
||||
|
||||
it('redirects /add/WETH-token to /add/WETH-address/token', () => {
|
||||
cy.visit('/add/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.url().should(
|
||||
'contain',
|
||||
'/add/0xc778417E063141139Fce010982780140Aa0cD5Ab/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85'
|
||||
)
|
||||
})
|
||||
|
||||
it('redirects /add/token-WETH to /add/token/WETH-address', () => {
|
||||
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85-0xc778417E063141139Fce010982780140Aa0cD5Ab')
|
||||
cy.url().should(
|
||||
'contain',
|
||||
'/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/0xc778417E063141139Fce010982780140Aa0cD5Ab'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -10,11 +10,6 @@ describe('Landing Page', () => {
|
||||
cy.url().should('include', '/swap')
|
||||
})
|
||||
|
||||
it('allows navigation to send', () => {
|
||||
cy.get('#send-nav-link').click()
|
||||
cy.url().should('include', '/send')
|
||||
})
|
||||
|
||||
it('allows navigation to pool', () => {
|
||||
cy.get('#pool-nav-link').click()
|
||||
cy.url().should('include', '/pool')
|
||||
|
||||
8
cypress/integration/migrate-v1.test.ts
Normal file
8
cypress/integration/migrate-v1.test.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
describe('Migrate V1 Liquidity', () => {
|
||||
describe('Remove V1 liquidity', () => {
|
||||
it('renders the correct page', () => {
|
||||
cy.visit('/remove/v1/0x93bB63aFe1E0180d0eF100D774B473034fd60C36')
|
||||
cy.get('#remove-v1-exchange').should('contain', 'MKR/ETH')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,13 +1,12 @@
|
||||
describe('Pool', () => {
|
||||
beforeEach(() => cy.visit('/pool'))
|
||||
it('can search for a pool', () => {
|
||||
it('add liquidity links to /add/ETH', () => {
|
||||
cy.get('#join-pool-button').click()
|
||||
cy.get('#token-search-input').type('DAI', { delay: 200 })
|
||||
cy.url().should('contain', '/add/ETH')
|
||||
})
|
||||
|
||||
it('can import a pool', () => {
|
||||
cy.get('#join-pool-button').click()
|
||||
cy.get('#import-pool-link').click({ force: true }) // blocked by the grid element in the search box
|
||||
cy.url().should('include', '/find')
|
||||
it('import pool links to /import', () => {
|
||||
cy.get('#import-pool-link').click()
|
||||
cy.url().should('contain', '/find')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,14 +1,34 @@
|
||||
describe('Remove Liquidity', () => {
|
||||
it('redirects', () => {
|
||||
cy.visit('/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.url().should(
|
||||
'contain',
|
||||
'/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85'
|
||||
)
|
||||
})
|
||||
|
||||
it('eth remove', () => {
|
||||
cy.visit('/remove/ETH/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'ETH')
|
||||
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR')
|
||||
})
|
||||
|
||||
it('eth remove swap order', () => {
|
||||
cy.visit('/remove/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/ETH')
|
||||
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'MKR')
|
||||
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'ETH')
|
||||
})
|
||||
|
||||
it('loads the two correct tokens', () => {
|
||||
cy.visit('/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'ETH')
|
||||
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'WETH')
|
||||
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR')
|
||||
})
|
||||
|
||||
it('does not crash if ETH is duplicated', () => {
|
||||
cy.visit('/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xc778417E063141139Fce010982780140Aa0cD5Ab')
|
||||
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'ETH')
|
||||
cy.get('#remove-liquidity-tokenb-symbol').should('not.contain.text', 'ETH')
|
||||
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'WETH')
|
||||
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'WETH')
|
||||
})
|
||||
|
||||
it('token not in storage is loaded', () => {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
describe('Send', () => {
|
||||
beforeEach(() => cy.visit('/send'))
|
||||
it('should redirect', () => {
|
||||
cy.visit('/send')
|
||||
cy.url().should('include', '/swap')
|
||||
})
|
||||
|
||||
it('can enter an amount into input', () => {
|
||||
cy.get('#sending-no-swap-input')
|
||||
.type('0.001', { delay: 200 })
|
||||
.should('have.value', '0.001')
|
||||
it('should redirect with url params', () => {
|
||||
cy.visit('/send?outputCurrency=ETH&recipient=bob.argent.xyz')
|
||||
cy.url().should('contain', '/swap?outputCurrency=ETH&recipient=bob.argent.xyz')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -40,4 +40,15 @@ describe('Swap', () => {
|
||||
cy.get('#swap-button').click()
|
||||
cy.get('#confirm-swap-or-send').should('contain', 'Confirm Swap')
|
||||
})
|
||||
|
||||
it('add a recipient', () => {
|
||||
cy.get('#add-recipient-button').click()
|
||||
cy.get('#recipient').should('exist')
|
||||
})
|
||||
|
||||
it('remove recipient', () => {
|
||||
cy.get('#add-recipient-button').click()
|
||||
cy.get('#remove-recipient-button').click()
|
||||
cy.get('#recipient').should('not.exist')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -69,11 +69,11 @@ class CustomizedBridge extends _Eip1193Bridge {
|
||||
|
||||
// sets up the injected provider to be a mock ethereum provider with the given mnemonic/index
|
||||
Cypress.Commands.overwrite('visit', (original, url, options) => {
|
||||
return original(url, {
|
||||
return original(url.startsWith('/') && url.length > 2 && !url.startsWith('/#') ? `/#${url}` : url, {
|
||||
...options,
|
||||
onBeforeLoad(win) {
|
||||
options && options.onBeforeLoad && options.onBeforeLoad(win)
|
||||
const provider = new JsonRpcProvider('https://rinkeby.infura.io/v3/acb7e55995d04c49bfb52b7141599467', 4)
|
||||
const provider = new JsonRpcProvider('https://rinkeby.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847', 4)
|
||||
const signer = new Wallet(PRIVATE_KEY_TEST_NEVER_USE, provider)
|
||||
win.ethereum = new CustomizedBridge(signer, provider)
|
||||
}
|
||||
|
||||
27
netlify.toml
27
netlify.toml
@@ -1,27 +0,0 @@
|
||||
# block some countries
|
||||
[[redirects]]
|
||||
from = "/*"
|
||||
to = "/451.html"
|
||||
status = 451
|
||||
force = true
|
||||
conditions = {Country=["BY","CU","IR","IQ","CI","LR","KP","SD","SY","ZW"]}
|
||||
headers = {Link="<https://uniswap.exchange>"}
|
||||
|
||||
# forward migrate
|
||||
[[redirects]]
|
||||
from = "https://migrate.uniswap.exchange/*"
|
||||
to = "https://uniswap.exchange/migrate/v1"
|
||||
status = 301
|
||||
force = true
|
||||
|
||||
# forward v2 subdomain to apex
|
||||
[[redirects]]
|
||||
from = "https://v2.uniswap.exchange/*"
|
||||
to = "https://uniswap.exchange/:splat"
|
||||
status = 301
|
||||
|
||||
# support SPA setup
|
||||
[[redirects]]
|
||||
from = "/*"
|
||||
to = "/index.html"
|
||||
status = 200
|
||||
35
package.json
35
package.json
@@ -1,19 +1,21 @@
|
||||
{
|
||||
"name": "@uniswap/interface",
|
||||
"description": "Uniswap Interface",
|
||||
"homepage": "https://uniswap.exchange",
|
||||
"homepage": ".",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@ethersproject/address": "^5.0.0-beta.134",
|
||||
"@ethersproject/bignumber": "^5.0.0-beta.138",
|
||||
"@ethersproject/constants": "^5.0.0-beta.133",
|
||||
"@ethersproject/contracts": "^5.0.0-beta.151",
|
||||
"@ethersproject/experimental": "^5.0.0-beta.141",
|
||||
"@ethersproject/address": "5.0.0-beta.134",
|
||||
"@ethersproject/bignumber": "5.0.0-beta.138",
|
||||
"@ethersproject/constants": "5.0.0-beta.133",
|
||||
"@ethersproject/contracts": "5.0.0-beta.151",
|
||||
"@ethersproject/experimental": "5.0.0-beta.141",
|
||||
"@ethersproject/networks": "5.0.0-beta.136",
|
||||
"@ethersproject/providers": "5.0.0-beta.162",
|
||||
"@ethersproject/strings": "^5.0.0-beta.136",
|
||||
"@ethersproject/units": "^5.0.0-beta.132",
|
||||
"@ethersproject/wallet": "^5.0.0-beta.141",
|
||||
"@popperjs/core": "^2.4.0",
|
||||
"@ethersproject/solidity": "5.0.2",
|
||||
"@ethersproject/strings": "5.0.0-beta.136",
|
||||
"@ethersproject/units": "5.0.0-beta.132",
|
||||
"@ethersproject/wallet": "5.0.0-beta.141",
|
||||
"@popperjs/core": "^2.4.4",
|
||||
"@reach/dialog": "^0.10.3",
|
||||
"@reach/portal": "^0.10.3",
|
||||
"@reduxjs/toolkit": "^1.3.5",
|
||||
@@ -27,11 +29,12 @@
|
||||
"@types/react-router-dom": "^5.0.0",
|
||||
"@types/react-window": "^1.8.2",
|
||||
"@types/rebass": "^4.0.5",
|
||||
"@types/styled-components": "^4.2.0",
|
||||
"@types/styled-components": "^5.1.0",
|
||||
"@types/testing-library__cypress": "^5.0.5",
|
||||
"@typescript-eslint/eslint-plugin": "^2.31.0",
|
||||
"@typescript-eslint/parser": "^2.31.0",
|
||||
"@uniswap/sdk": "^2.0.5",
|
||||
"@uniswap/sdk": "3.0.3-beta.1",
|
||||
"@uniswap/token-lists": "^1.0.0-beta.9",
|
||||
"@uniswap/v2-core": "1.0.0",
|
||||
"@uniswap/v2-periphery": "^1.1.0-beta.0",
|
||||
"@web3-react/core": "^6.0.9",
|
||||
@@ -40,6 +43,7 @@
|
||||
"@web3-react/portis-connector": "^6.0.9",
|
||||
"@web3-react/walletconnect-connector": "^6.1.1",
|
||||
"@web3-react/walletlink-connector": "^6.0.9",
|
||||
"ajv": "^6.12.3",
|
||||
"copy-to-clipboard": "^3.2.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"cypress": "^4.5.0",
|
||||
@@ -51,6 +55,7 @@
|
||||
"i18next": "^15.0.9",
|
||||
"i18next-browser-languagedetector": "^3.0.1",
|
||||
"i18next-xhr-backend": "^2.0.1",
|
||||
"inter-ui": "^3.13.1",
|
||||
"jazzicon": "^1.5.0",
|
||||
"lodash.flatmap": "^4.5.0",
|
||||
"polished": "^3.3.2",
|
||||
@@ -81,14 +86,10 @@
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"ipfs-build": "cross-env PUBLIC_URL=\".\" react-scripts build",
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject",
|
||||
"lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
|
||||
"lint:fix": "eslint 'src/**/*.{js,jsx,ts,tsx}' --fix",
|
||||
"cy:run": "cypress run",
|
||||
"serve:build": "serve -s build -l 3000",
|
||||
"integration-test": "yarn build && start-server-and-test 'yarn run serve:build' http://localhost:3000 cy:run"
|
||||
"integration-test": "start-server-and-test 'serve build -l 3000' http://localhost:3000 'cypress run'"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
{
|
||||
"noWallet": "לא נמצא ארנק",
|
||||
"wrongNetwork": "נבחרה רשת לא נכונה",
|
||||
"switchNetwork": "{{ correctNetwork }} יש צורך לשנות את הרשת ל",
|
||||
"installWeb3MobileBrowser": "יש צורך בארנק ווב3.0, תתקין מטאמאסק או ארנק דומה",
|
||||
"installMetamask": " Metamask יש צורך להתקין תוסף מטאמאסק לדפדפן, חפשו בגוגל ",
|
||||
"disconnected": "מנותק",
|
||||
"swap": "המרה",
|
||||
"send": "שליחה",
|
||||
"pool": "להפקיד",
|
||||
"betaWarning": "הפרויקט נמצא בשלב בטא, השתמשו באחריות",
|
||||
"input": "מוכר",
|
||||
"output": "אקבל",
|
||||
"estimated": "הערכה",
|
||||
"balance": "בארנק שלי {{ balanceInput }}",
|
||||
"unlock": "שחרור נעילת ארנק",
|
||||
"pending": "ממתין לאישור",
|
||||
"selectToken": "בחרו את הטוקן להמרה",
|
||||
"searchOrPaste": "הכניסו שם או כתובת של טוקן לחיפוש",
|
||||
"noExchange": "לא מתאפשרת המרה",
|
||||
"exchangeRate": "שער המרה",
|
||||
"enterValueCont": "כדי להמשיך {{ missingCurrencyValue }} הזינו ",
|
||||
"selectTokenCont": "בחרו טוקן כדי להמשיך",
|
||||
"noLiquidity": "אין נזילות",
|
||||
"unlockTokenCont": "יש צורך לאשר את הטוקן למסחר",
|
||||
"transactionDetails": "פרטי הטרנזקציה",
|
||||
"hideDetails": "הסתר פרטים נוספים",
|
||||
"youAreSelling": "למכירה",
|
||||
"orTransFail": "או שהטרנזקציה תיכשל",
|
||||
"youWillReceive": "תוצר המרה מינימלי",
|
||||
"youAreBuying": "קונה",
|
||||
"itWillCost": "זה יעלה",
|
||||
"insufficientBalance": "אין בחשבון מספיק מטבעות",
|
||||
"inputNotValid": "קלט לא תקין",
|
||||
"differentToken": "יש צורך בטוקנים שונים",
|
||||
"noRecipient": "לא הוכנסה כתובת ארנק יעד",
|
||||
"invalidRecipient": "לא הוכנסה כתובת תקינה",
|
||||
"recipientAddress": "כתובת יעד",
|
||||
"youAreSending": "כמות לשליחה",
|
||||
"willReceive": "יתקבל לכל הפחות",
|
||||
"to": "אל",
|
||||
"addLiquidity": "להוספת נזילות למאגר",
|
||||
"deposit": "הפקדה",
|
||||
"currentPoolSize": "גודל מאגר הנזילות הכולל",
|
||||
"yourPoolShare": "חלקך במאגר הנזילות",
|
||||
"noZero": "אפס אינו ערך תקין",
|
||||
"mustBeETH": "ETH חייב להופיע באחד מהצדדים",
|
||||
"enterCurrencyOrLabelCont": "כדי להמשיך {{ inputCurrency }} או {{ label }} הכנס",
|
||||
"youAreAdding": "מתווספים למאגר",
|
||||
"and": "וגם",
|
||||
"intoPool": "לתוך הנזילות",
|
||||
"outPool": "מתוך",
|
||||
"youWillMint": "יונפקו לכם",
|
||||
"liquidityTokens": "טוקנים של נזילות",
|
||||
"totalSupplyIs": "חלקך במאגר הנזילות",
|
||||
"youAreSettingExRate": "שער ההמרה יקבע על ידך",
|
||||
"totalSupplyIs0": "אין לך טוקנים של נזילות",
|
||||
"tokenWorth": "שווי כל טוקן נזילות הינו",
|
||||
"firstLiquidity": "את\ה הראשון\ה שמזרים נזילות למאגר",
|
||||
"initialExchangeRate": "ושל האית'ר הינן בערך שווה {{ label }} תוודאו שההפקדה של הטוקן",
|
||||
"removeLiquidity": "הוצאה של נזילות",
|
||||
"poolTokens": "טוקנים של מאגר הנזילות",
|
||||
"enterLabelCont": "כדי להמשיך {{ label }} הכנס ",
|
||||
"youAreRemoving": "יוסרו",
|
||||
"youWillRemove": "יוסרו",
|
||||
"createExchange": "ליצירת זוג מסחר",
|
||||
"invalidTokenAddress": "כתובת טוקן לא נכונה",
|
||||
"exchangeExists": "{{ label }} כבר קיים זוג המרה עבור",
|
||||
"invalidSymbol": "תו שגוי",
|
||||
"invalidDecimals": "ספרות עשרוניות שגויות",
|
||||
"tokenAddress": "כתובת הטוקן",
|
||||
"label": "שם",
|
||||
"decimals": "ספרות עשרויות",
|
||||
"enterTokenCont": "הכניסו כתובת טוקן כדי להמשיך"
|
||||
}
|
||||
@@ -56,7 +56,7 @@
|
||||
"youAreSettingExRate": "שער ההמרה יקבע על ידך",
|
||||
"totalSupplyIs0": "אין לך טוקנים של נזילות",
|
||||
"tokenWorth": "שווי כל טוקן נזילות הינו",
|
||||
"firstLiquidity": "את\ה הראשון\ה שמזרים נזילות למאגר",
|
||||
"firstLiquidity": "אתה הראשוןה שמזרים נזילות למאגר",
|
||||
"initialExchangeRate": "ושל האית'ר הינן בערך שווה {{ label }} תוודאו שההפקדה של הטוקן",
|
||||
"removeLiquidity": "הוצאה של נזילות",
|
||||
"poolTokens": "טוקנים של מאגר הנזילות",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { CheckCircle, Triangle, ExternalLink as LinkIcon } from 'react-feather'
|
||||
import { CheckCircle, Triangle } from 'react-feather'
|
||||
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { getEtherscanLink } from '../../utils'
|
||||
@@ -50,8 +50,7 @@ export default function Transaction({ hash }: { hash: string }) {
|
||||
<TransactionWrapper>
|
||||
<TransactionState href={getEtherscanLink(chainId, hash, 'transaction')} pending={pending} success={success}>
|
||||
<RowFixed>
|
||||
<TransactionStatusText>{summary ? summary : hash}</TransactionStatusText>
|
||||
<LinkIcon size={16} />
|
||||
<TransactionStatusText>{summary ?? hash} ↗</TransactionStatusText>
|
||||
</RowFixed>
|
||||
<IconWrapper pending={pending} success={success}>
|
||||
{pending ? <Loader /> : success ? <CheckCircle size="16" /> : <Triangle size="16" />}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import React, { useState, useEffect, useContext } from 'react'
|
||||
import React, { useContext, useCallback } from 'react'
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
import useDebounce from '../../hooks/useDebounce'
|
||||
|
||||
import { isAddress } from '../../utils'
|
||||
import useENS from '../../hooks/useENS'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { ExternalLink, TYPE } from '../../theme'
|
||||
import { AutoColumn } from '../Column'
|
||||
@@ -24,6 +22,8 @@ const ContainerRow = styled.div<{ error: boolean }>`
|
||||
align-items: center;
|
||||
border-radius: 1.25rem;
|
||||
border: 1px solid ${({ error, theme }) => (error ? theme.red1 : theme.bg2)};
|
||||
transition: border-color 300ms ${({ error }) => (error ? 'step-end' : 'step-start')},
|
||||
color 500ms ${({ error }) => (error ? 'step-end' : 'step-start')};
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
`
|
||||
|
||||
@@ -39,6 +39,7 @@ const Input = styled.input<{ error?: boolean }>`
|
||||
flex: 1 1 auto;
|
||||
width: 0;
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
transition: color 300ms ${({ error }) => (error ? 'step-end' : 'step-start')};
|
||||
color: ${({ error, theme }) => (error ? theme.red1 : theme.primary1)};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -64,120 +65,65 @@ const Input = styled.input<{ error?: boolean }>`
|
||||
}
|
||||
`
|
||||
|
||||
interface Value {
|
||||
address: string
|
||||
name?: string
|
||||
}
|
||||
|
||||
export default function AddressInputPanel({
|
||||
initialInput = '',
|
||||
onChange,
|
||||
onError
|
||||
id,
|
||||
value,
|
||||
onChange
|
||||
}: {
|
||||
initialInput?: string
|
||||
onChange: (val: { address: string; name?: string }) => void
|
||||
onError: (error: boolean, input: string) => void
|
||||
id?: string
|
||||
// the typed string value
|
||||
value: string
|
||||
// triggers whenever the typed value changes
|
||||
onChange: (value: string) => void
|
||||
}) {
|
||||
const { chainId, library } = useActiveWeb3React()
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
const [input, setInput] = useState(initialInput ? initialInput : '')
|
||||
const debouncedInput = useDebounce(input, 200)
|
||||
const { address, loading, name } = useENS(value)
|
||||
|
||||
const [data, setData] = useState<{ address: string; name: string }>({ address: undefined, name: undefined })
|
||||
const [error, setError] = useState<boolean>(false)
|
||||
const handleInput = useCallback(
|
||||
event => {
|
||||
const input = event.target.value
|
||||
const withoutSpaces = input.replace(/\s+/g, '')
|
||||
onChange(withoutSpaces)
|
||||
},
|
||||
[onChange]
|
||||
)
|
||||
|
||||
// keep data and errors in sync
|
||||
useEffect(() => {
|
||||
onChange({ address: data.address, name: data.name })
|
||||
}, [onChange, data.address, data.name])
|
||||
useEffect(() => {
|
||||
onError(error, input)
|
||||
}, [onError, error, input])
|
||||
|
||||
// run parser on debounced input
|
||||
useEffect(() => {
|
||||
let stale = false
|
||||
// if the input is an address, try to look up its name
|
||||
if (isAddress(debouncedInput)) {
|
||||
library
|
||||
.lookupAddress(debouncedInput)
|
||||
.then(name => {
|
||||
if (stale) return
|
||||
// if an ENS name exists, set it as the destination
|
||||
if (name) {
|
||||
setInput(name)
|
||||
} else {
|
||||
setData({ address: debouncedInput, name: '' })
|
||||
setError(null)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (stale) return
|
||||
setData({ address: debouncedInput, name: '' })
|
||||
setError(null)
|
||||
})
|
||||
}
|
||||
// otherwise try to look up the address of the input, treated as an ENS name
|
||||
else {
|
||||
if (debouncedInput !== '') {
|
||||
library
|
||||
.resolveName(debouncedInput)
|
||||
.then(address => {
|
||||
if (stale) return
|
||||
// if the debounced input name resolves to an address
|
||||
if (address) {
|
||||
setData({ address: address, name: debouncedInput })
|
||||
setError(null)
|
||||
} else {
|
||||
setError(true)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (stale) return
|
||||
setError(true)
|
||||
})
|
||||
} else if (debouncedInput === '') {
|
||||
setError(true)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
stale = true
|
||||
}
|
||||
}, [debouncedInput, library])
|
||||
|
||||
function onInput(event) {
|
||||
setData({ address: undefined, name: undefined })
|
||||
setError(false)
|
||||
const input = event.target.value
|
||||
const checksummedInput = isAddress(input.replace(/\s/g, '')) // delete whitespace
|
||||
setInput(checksummedInput || input)
|
||||
}
|
||||
const error = Boolean(value.length > 0 && !loading && !address)
|
||||
|
||||
return (
|
||||
<InputPanel>
|
||||
<ContainerRow error={input !== '' && error}>
|
||||
<InputPanel id={id}>
|
||||
<ContainerRow error={error}>
|
||||
<InputContainer>
|
||||
<AutoColumn gap="md">
|
||||
<RowBetween>
|
||||
<TYPE.black color={theme.text2} fontWeight={500} fontSize={14}>
|
||||
Recipient
|
||||
</TYPE.black>
|
||||
{data.address && (
|
||||
<ExternalLink
|
||||
href={getEtherscanLink(chainId, data.name || data.address, 'address')}
|
||||
style={{ fontSize: '14px' }}
|
||||
>
|
||||
{address && (
|
||||
<ExternalLink href={getEtherscanLink(chainId, name ?? address, 'address')} style={{ fontSize: '14px' }}>
|
||||
(View on Etherscan)
|
||||
</ExternalLink>
|
||||
)}
|
||||
</RowBetween>
|
||||
<Input
|
||||
className="recipient-address-input"
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
placeholder="Wallet Address or ENS name"
|
||||
error={input !== '' && error}
|
||||
onChange={onInput}
|
||||
value={input}
|
||||
error={error}
|
||||
pattern="^(0x[a-fA-F0-9]{40})$"
|
||||
onChange={handleInput}
|
||||
value={value}
|
||||
/>
|
||||
</AutoColumn>
|
||||
</InputContainer>
|
||||
|
||||
@@ -21,6 +21,7 @@ const Base = styled(RebassButton)<{
|
||||
outline: none;
|
||||
border: 1px solid transparent;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { Pair, Token } from '@uniswap/sdk'
|
||||
import { Currency, Pair } from '@uniswap/sdk'
|
||||
import React, { useState, useContext, useCallback } from 'react'
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
import { darken } from 'polished'
|
||||
import { Field } from '../../state/swap/actions'
|
||||
import { useTokenBalanceTreatingWETHasETH } from '../../state/wallet/hooks'
|
||||
import TokenSearchModal from '../SearchModal/TokenSearchModal'
|
||||
import TokenLogo from '../TokenLogo'
|
||||
import DoubleLogo from '../DoubleLogo'
|
||||
import { useCurrencyBalance } from '../../state/wallet/hooks'
|
||||
import CurrencySearchModal from '../SearchModal/CurrencySearchModal'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import DoubleCurrencyLogo from '../DoubleLogo'
|
||||
import { RowBetween } from '../Row'
|
||||
import { TYPE, CursorPointer } from '../../theme'
|
||||
import { Input as NumericalInput } from '../NumericalInput'
|
||||
@@ -117,46 +116,44 @@ const StyledBalanceMax = styled.button`
|
||||
|
||||
interface CurrencyInputPanelProps {
|
||||
value: string
|
||||
field: string
|
||||
onUserInput: (field: string, val: string) => void
|
||||
onUserInput: (value: string) => void
|
||||
onMax?: () => void
|
||||
showMaxButton: boolean
|
||||
label?: string
|
||||
onTokenSelection?: (tokenAddress: string) => void
|
||||
token?: Token | null
|
||||
disableTokenSelect?: boolean
|
||||
onCurrencySelect?: (currency: Currency) => void
|
||||
currency?: Currency | null
|
||||
disableCurrencySelect?: boolean
|
||||
hideBalance?: boolean
|
||||
isExchange?: boolean
|
||||
pair?: Pair | null
|
||||
hideInput?: boolean
|
||||
showSendWithSwap?: boolean
|
||||
otherSelectedTokenAddress?: string | null
|
||||
otherCurrency?: Currency | null
|
||||
id: string
|
||||
showCommonBases?: boolean
|
||||
}
|
||||
|
||||
export default function CurrencyInputPanel({
|
||||
value,
|
||||
field,
|
||||
onUserInput,
|
||||
onMax,
|
||||
showMaxButton,
|
||||
label = 'Input',
|
||||
onTokenSelection = null,
|
||||
token = null,
|
||||
disableTokenSelect = false,
|
||||
onCurrencySelect = null,
|
||||
currency = null,
|
||||
disableCurrencySelect = false,
|
||||
hideBalance = false,
|
||||
isExchange = false,
|
||||
pair = null, // used for double token logo
|
||||
hideInput = false,
|
||||
showSendWithSwap = false,
|
||||
otherSelectedTokenAddress = null,
|
||||
id
|
||||
otherCurrency = null,
|
||||
id,
|
||||
showCommonBases
|
||||
}: CurrencyInputPanelProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [modalOpen, setModalOpen] = useState(false)
|
||||
const { account } = useActiveWeb3React()
|
||||
const userTokenBalance = useTokenBalanceTreatingWETHasETH(account, token)
|
||||
const selectedCurrencyBalance = useCurrencyBalance(account, currency)
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
const handleDismissSearch = useCallback(() => {
|
||||
@@ -181,8 +178,8 @@ export default function CurrencyInputPanel({
|
||||
fontSize={14}
|
||||
style={{ display: 'inline' }}
|
||||
>
|
||||
{!hideBalance && !!token && userTokenBalance
|
||||
? 'Balance: ' + userTokenBalance?.toSignificant(6)
|
||||
{!hideBalance && !!currency && selectedCurrencyBalance
|
||||
? 'Balance: ' + selectedCurrencyBalance?.toSignificant(6)
|
||||
: ' -'}
|
||||
</TYPE.body>
|
||||
</CursorPointer>
|
||||
@@ -190,63 +187,63 @@ export default function CurrencyInputPanel({
|
||||
</RowBetween>
|
||||
</LabelRow>
|
||||
)}
|
||||
<InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}} selected={disableTokenSelect}>
|
||||
<InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}} selected={disableCurrencySelect}>
|
||||
{!hideInput && (
|
||||
<>
|
||||
<NumericalInput
|
||||
className="token-amount-input"
|
||||
value={value}
|
||||
onUserInput={val => {
|
||||
onUserInput(field, val)
|
||||
onUserInput(val)
|
||||
}}
|
||||
/>
|
||||
{account && !!token?.address && showMaxButton && label !== 'To' && (
|
||||
{account && currency && showMaxButton && label !== 'To' && (
|
||||
<StyledBalanceMax onClick={onMax}>MAX</StyledBalanceMax>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<CurrencySelect
|
||||
selected={!!token}
|
||||
selected={!!currency}
|
||||
className="open-currency-select-button"
|
||||
onClick={() => {
|
||||
if (!disableTokenSelect) {
|
||||
if (!disableCurrencySelect) {
|
||||
setModalOpen(true)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Aligner>
|
||||
{isExchange ? (
|
||||
<DoubleLogo a0={pair?.token0.address} a1={pair?.token1.address} size={24} margin={true} />
|
||||
) : token?.address ? (
|
||||
<TokenLogo address={token?.address} size={'24px'} />
|
||||
{pair ? (
|
||||
<DoubleCurrencyLogo currency0={pair.token0} currency1={pair.token1} size={24} margin={true} />
|
||||
) : currency ? (
|
||||
<CurrencyLogo currency={currency} size={'24px'} />
|
||||
) : null}
|
||||
{isExchange ? (
|
||||
{pair ? (
|
||||
<StyledTokenName className="pair-name-container">
|
||||
{pair?.token0.symbol}:{pair?.token1.symbol}
|
||||
</StyledTokenName>
|
||||
) : (
|
||||
<StyledTokenName className="token-symbol-container" active={Boolean(token && token.symbol)}>
|
||||
{(token && token.symbol && token.symbol.length > 20
|
||||
? token.symbol.slice(0, 4) +
|
||||
<StyledTokenName className="token-symbol-container" active={Boolean(currency && currency.symbol)}>
|
||||
{(currency && currency.symbol && currency.symbol.length > 20
|
||||
? currency.symbol.slice(0, 4) +
|
||||
'...' +
|
||||
token.symbol.slice(token.symbol.length - 5, token.symbol.length)
|
||||
: token?.symbol) || t('selectToken')}
|
||||
currency.symbol.slice(currency.symbol.length - 5, currency.symbol.length)
|
||||
: currency?.symbol) || t('selectToken')}
|
||||
</StyledTokenName>
|
||||
)}
|
||||
{!disableTokenSelect && <StyledDropDown selected={!!token?.address} />}
|
||||
{!disableCurrencySelect && <StyledDropDown selected={!!currency} />}
|
||||
</Aligner>
|
||||
</CurrencySelect>
|
||||
</InputRow>
|
||||
</Container>
|
||||
{!disableTokenSelect && (
|
||||
<TokenSearchModal
|
||||
{!disableCurrencySelect && (
|
||||
<CurrencySearchModal
|
||||
isOpen={modalOpen}
|
||||
onDismiss={handleDismissSearch}
|
||||
onTokenSelect={onTokenSelection}
|
||||
onCurrencySelect={onCurrencySelect}
|
||||
showSendWithSwap={showSendWithSwap}
|
||||
hiddenToken={token?.address}
|
||||
otherSelectedTokenAddress={otherSelectedTokenAddress}
|
||||
otherSelectedText={field === Field.INPUT ? 'Selected as output' : 'Selected as input'}
|
||||
hiddenCurrency={currency}
|
||||
otherSelectedCurrency={otherCurrency}
|
||||
showCommonBases={showCommonBases}
|
||||
/>
|
||||
)}
|
||||
</InputPanel>
|
||||
|
||||
94
src/components/CurrencyLogo/index.tsx
Normal file
94
src/components/CurrencyLogo/index.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import { Currency, ETHER, Token } from '@uniswap/sdk'
|
||||
import React, { useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import EthereumLogo from '../../assets/images/ethereum-logo.png'
|
||||
import { WrappedTokenInfo } from '../../state/lists/hooks'
|
||||
import uriToHttp from '../../utils/uriToHttp'
|
||||
|
||||
const getTokenLogoURL = address =>
|
||||
`https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/${address}/logo.png`
|
||||
const BAD_URIS: { [tokenAddress: string]: true } = {}
|
||||
|
||||
const Image = styled.img<{ size: string }>`
|
||||
width: ${({ size }) => size};
|
||||
height: ${({ size }) => size};
|
||||
background-color: white;
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.075);
|
||||
`
|
||||
|
||||
const Emoji = styled.span<{ size?: string }>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: ${({ size }) => size};
|
||||
width: ${({ size }) => size};
|
||||
height: ${({ size }) => size};
|
||||
margin-bottom: -4px;
|
||||
`
|
||||
|
||||
const StyledEthereumLogo = styled.img<{ size: string }>`
|
||||
width: ${({ size }) => size};
|
||||
height: ${({ size }) => size};
|
||||
box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.075);
|
||||
border-radius: 24px;
|
||||
`
|
||||
|
||||
export default function CurrencyLogo({
|
||||
currency,
|
||||
size = '24px',
|
||||
...rest
|
||||
}: {
|
||||
currency?: Currency
|
||||
size?: string
|
||||
style?: React.CSSProperties
|
||||
}) {
|
||||
const [, refresh] = useState<number>(0)
|
||||
|
||||
if (currency === ETHER) {
|
||||
return <StyledEthereumLogo src={EthereumLogo} size={size} {...rest} />
|
||||
}
|
||||
|
||||
if (currency instanceof Token) {
|
||||
let uri: string | undefined
|
||||
|
||||
if (currency instanceof WrappedTokenInfo) {
|
||||
if (currency.logoURI && !BAD_URIS[currency.logoURI]) {
|
||||
uri = uriToHttp(currency.logoURI).filter(s => !BAD_URIS[s])[0]
|
||||
}
|
||||
}
|
||||
|
||||
if (!uri) {
|
||||
const defaultUri = getTokenLogoURL(currency.address)
|
||||
if (!BAD_URIS[defaultUri]) {
|
||||
uri = defaultUri
|
||||
}
|
||||
}
|
||||
|
||||
if (uri) {
|
||||
return (
|
||||
<Image
|
||||
{...rest}
|
||||
alt={`${currency.name} Logo`}
|
||||
src={uri}
|
||||
size={size}
|
||||
onError={() => {
|
||||
if (currency instanceof Token) {
|
||||
BAD_URIS[uri] = true
|
||||
}
|
||||
refresh(i => i + 1)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Emoji {...rest} size={size}>
|
||||
<span role="img" aria-label="Thinking">
|
||||
🤔
|
||||
</span>
|
||||
</Emoji>
|
||||
)
|
||||
}
|
||||
@@ -1,34 +1,40 @@
|
||||
import { Currency } from '@uniswap/sdk'
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import TokenLogo from '../TokenLogo'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
|
||||
const TokenWrapper = styled.div<{ margin: boolean; sizeraw: number }>`
|
||||
const Wrapper = styled.div<{ margin: boolean; sizeraw: number }>`
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-right: ${({ sizeraw, margin }) => margin && (sizeraw / 3 + 8).toString() + 'px'};
|
||||
`
|
||||
|
||||
interface DoubleTokenLogoProps {
|
||||
interface DoubleCurrencyLogoProps {
|
||||
margin?: boolean
|
||||
size?: number
|
||||
a0: string
|
||||
a1?: string
|
||||
currency0?: Currency
|
||||
currency1?: Currency
|
||||
}
|
||||
|
||||
const HigherLogo = styled(TokenLogo)`
|
||||
const HigherLogo = styled(CurrencyLogo)`
|
||||
z-index: 2;
|
||||
`
|
||||
const CoveredLogo = styled(TokenLogo)<{ sizeraw: number }>`
|
||||
const CoveredLogo = styled(CurrencyLogo)<{ sizeraw: number }>`
|
||||
position: absolute;
|
||||
left: ${({ sizeraw }) => (sizeraw / 2).toString() + 'px'};
|
||||
`
|
||||
|
||||
export default function DoubleTokenLogo({ a0, a1, size = 16, margin = false }: DoubleTokenLogoProps) {
|
||||
export default function DoubleCurrencyLogo({
|
||||
currency0,
|
||||
currency1,
|
||||
size = 16,
|
||||
margin = false
|
||||
}: DoubleCurrencyLogoProps) {
|
||||
return (
|
||||
<TokenWrapper sizeraw={size} margin={margin}>
|
||||
<HigherLogo address={a0} size={size.toString() + 'px'} />
|
||||
{a1 && <CoveredLogo address={a1} size={size.toString() + 'px'} sizeraw={size} />}
|
||||
</TokenWrapper>
|
||||
<Wrapper sizeraw={size} margin={margin}>
|
||||
{currency0 && <HigherLogo currency={currency0} size={size.toString() + 'px'} />}
|
||||
{currency1 && <CoveredLogo currency={currency1} size={size.toString() + 'px'} sizeraw={size} />}
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { Send, Sun, Moon } from 'react-feather'
|
||||
import { useDarkModeManager } from '../../state/user/hooks'
|
||||
|
||||
import { ButtonSecondary } from '../Button'
|
||||
|
||||
const FooterFrame = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
position: fixed;
|
||||
right: 1rem;
|
||||
bottom: 1rem;
|
||||
${({ theme }) => theme.mediaWidth.upToExtraSmall`
|
||||
display: none;
|
||||
`};
|
||||
`
|
||||
|
||||
export default function Footer() {
|
||||
const [darkMode, toggleDarkMode] = useDarkModeManager()
|
||||
|
||||
return (
|
||||
<FooterFrame>
|
||||
<form action="https://forms.gle/DaLuqvJsVhVaAM3J9" target="_blank">
|
||||
<ButtonSecondary p="8px 12px">
|
||||
<Send size={16} style={{ marginRight: '8px' }} /> Feedback
|
||||
</ButtonSecondary>
|
||||
</form>
|
||||
<ButtonSecondary onClick={toggleDarkMode} p="8px 12px" ml="0.5rem" width="min-content">
|
||||
{darkMode ? <Sun size={16} /> : <Moon size={16} />}
|
||||
</ButtonSecondary>
|
||||
</FooterFrame>
|
||||
)
|
||||
}
|
||||
@@ -4,18 +4,19 @@ import { Link, useLocation } from 'react-router-dom'
|
||||
import styled from 'styled-components'
|
||||
import useParsedQueryString from '../../hooks/useParsedQueryString'
|
||||
import useToggledVersion, { Version } from '../../hooks/useToggledVersion'
|
||||
import { MouseoverTooltip } from '../Tooltip'
|
||||
|
||||
const VersionLabel = styled.span<{ enabled: boolean }>`
|
||||
padding: ${({ enabled }) => (enabled ? '0.15rem 0.5rem 0.16rem 0.45rem' : '0.15rem 0.5rem 0.16rem 0.35rem')};
|
||||
border-radius: 14px;
|
||||
padding: 0.35rem 0.6rem;
|
||||
border-radius: 12px;
|
||||
background: ${({ theme, enabled }) => (enabled ? theme.primary1 : 'none')};
|
||||
color: ${({ theme, enabled }) => (enabled ? theme.white : theme.primary1)};
|
||||
font-size: 0.825rem;
|
||||
font-weight: 400;
|
||||
color: ${({ theme, enabled }) => (enabled ? theme.white : theme.text1)};
|
||||
font-size: 1rem;
|
||||
font-weight: ${({ enabled }) => (enabled ? '500' : '400')};
|
||||
:hover {
|
||||
user-select: ${({ enabled }) => (enabled ? 'none' : 'initial')};
|
||||
background: ${({ theme, enabled }) => (enabled ? theme.primary1 : 'none')};
|
||||
color: ${({ theme, enabled }) => (enabled ? theme.white : theme.primary3)};
|
||||
color: ${({ theme, enabled }) => (enabled ? theme.white : theme.text1)};
|
||||
}
|
||||
`
|
||||
|
||||
@@ -25,21 +26,21 @@ interface VersionToggleProps extends React.ComponentProps<typeof Link> {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const VersionToggle = styled(({ enabled, ...rest }: VersionToggleProps) => <Link {...rest} />)<VersionToggleProps>`
|
||||
border-radius: 16px;
|
||||
border-radius: 12px;
|
||||
opacity: ${({ enabled }) => (enabled ? 1 : 0.5)};
|
||||
cursor: ${({ enabled }) => (enabled ? 'pointer' : 'default')};
|
||||
background: ${({ theme }) => theme.primary5};
|
||||
border: 1px solid ${({ theme }) => theme.primary4};
|
||||
background: ${({ theme }) => theme.bg3};
|
||||
color: ${({ theme }) => theme.primary1};
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
margin-left: 0.5rem;
|
||||
text-decoration: none;
|
||||
:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
`
|
||||
|
||||
export function VersionSwitch() {
|
||||
export default function VersionSwitch() {
|
||||
const version = useToggledVersion()
|
||||
const location = useLocation()
|
||||
const query = useParsedQueryString()
|
||||
@@ -61,10 +62,15 @@ export function VersionSwitch() {
|
||||
[versionSwitchAvailable]
|
||||
)
|
||||
|
||||
return (
|
||||
const toggle = (
|
||||
<VersionToggle enabled={versionSwitchAvailable} to={toggleDest} onClick={handleClick}>
|
||||
<VersionLabel enabled={version === Version.v2 || !versionSwitchAvailable}>V2</VersionLabel>
|
||||
<VersionLabel enabled={version === Version.v1 && versionSwitchAvailable}>V1</VersionLabel>
|
||||
</VersionToggle>
|
||||
)
|
||||
return versionSwitchAvailable ? (
|
||||
toggle
|
||||
) : (
|
||||
<MouseoverTooltip text="This page is only compatible with Uniswap V2.">{toggle}</MouseoverTooltip>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ChainId, WETH } from '@uniswap/sdk'
|
||||
import { ChainId } from '@uniswap/sdk'
|
||||
import React from 'react'
|
||||
import { isMobile } from 'react-device-detect'
|
||||
import { Link as HistoryLink } from 'react-router-dom'
|
||||
import { Text } from 'rebass'
|
||||
|
||||
import styled from 'styled-components'
|
||||
@@ -12,17 +11,15 @@ import Wordmark from '../../assets/svg/wordmark.svg'
|
||||
import WordmarkDark from '../../assets/svg/wordmark_white.svg'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useDarkModeManager } from '../../state/user/hooks'
|
||||
import { useTokenBalanceTreatingWETHasETH } from '../../state/wallet/hooks'
|
||||
import { useETHBalances } from '../../state/wallet/hooks'
|
||||
|
||||
import { ExternalLink, StyledInternalLink } from '../../theme'
|
||||
import { YellowCard } from '../Card'
|
||||
import { AutoColumn } from '../Column'
|
||||
import Settings from '../Settings'
|
||||
import Menu from '../Menu'
|
||||
|
||||
import Row, { RowBetween } from '../Row'
|
||||
import Web3Status from '../Web3Status'
|
||||
import { VersionSwitch } from './VersionSwitch'
|
||||
import VersionSwitch from './VersionSwitch'
|
||||
|
||||
const HeaderFrame = styled.div`
|
||||
display: flex;
|
||||
@@ -45,7 +42,16 @@ const HeaderElement = styled.div`
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const Title = styled.div`
|
||||
const HeaderElementWrap = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
margin-top: 0.5rem;
|
||||
`};
|
||||
`
|
||||
|
||||
const Title = styled.a`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
pointer-events: auto;
|
||||
@@ -70,6 +76,7 @@ const AccountElement = styled.div<{ active: boolean }>`
|
||||
background-color: ${({ theme, active }) => (!active ? theme.bg1 : theme.bg3)};
|
||||
border-radius: 12px;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
|
||||
:focus {
|
||||
border: 1px solid blue;
|
||||
@@ -80,10 +87,7 @@ const TestnetWrapper = styled.div`
|
||||
white-space: nowrap;
|
||||
width: fit-content;
|
||||
margin-left: 10px;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
display: none;
|
||||
`};
|
||||
pointer-events: auto;
|
||||
`
|
||||
|
||||
const NetworkCard = styled(YellowCard)`
|
||||
@@ -93,29 +97,31 @@ const NetworkCard = styled(YellowCard)`
|
||||
padding: 8px 12px;
|
||||
`
|
||||
|
||||
const UniIcon = styled(HistoryLink)<{ to: string }>`
|
||||
const UniIcon = styled.div`
|
||||
transition: transform 0.3s ease;
|
||||
:hover {
|
||||
transform: rotate(-5deg);
|
||||
}
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
img {
|
||||
width: 4.5rem;
|
||||
}
|
||||
`};
|
||||
`
|
||||
|
||||
const MigrateBanner = styled(AutoColumn)`
|
||||
width: 100%;
|
||||
padding: 12px 0;
|
||||
const HeaderControls = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background-color: ${({ theme }) => theme.primary5};
|
||||
color: ${({ theme }) => theme.primaryText1};
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
pointer-events: auto;
|
||||
a {
|
||||
color: ${({ theme }) => theme.primaryText1};
|
||||
}
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
padding: 0;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
`};
|
||||
`
|
||||
|
||||
const BalanceText = styled(Text)`
|
||||
${({ theme }) => theme.mediaWidth.upToExtraSmall`
|
||||
display: none;
|
||||
`};
|
||||
`
|
||||
@@ -128,68 +134,45 @@ const NETWORK_LABELS: { [chainId in ChainId]: string | null } = {
|
||||
[ChainId.KOVAN]: 'Kovan'
|
||||
}
|
||||
|
||||
const BalanceWrapper = styled.div`
|
||||
${({ theme }) => theme.mediaWidth.upToExtraSmall`
|
||||
display: none;
|
||||
`};
|
||||
`
|
||||
|
||||
export default function Header() {
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
|
||||
const userEthBalance = useTokenBalanceTreatingWETHasETH(account, WETH[chainId])
|
||||
const userEthBalance = useETHBalances([account])[account]
|
||||
const [isDark] = useDarkModeManager()
|
||||
|
||||
return (
|
||||
<HeaderFrame>
|
||||
<MigrateBanner>
|
||||
Uniswap V2 is live! Read the
|
||||
<ExternalLink href="https://uniswap.org/blog/launch-uniswap-v2/">
|
||||
<b>blog post ↗</b>
|
||||
</ExternalLink>
|
||||
or
|
||||
<StyledInternalLink to="/migrate/v1">
|
||||
<b>migrate your liquidity ↗</b>
|
||||
</StyledInternalLink>
|
||||
.
|
||||
</MigrateBanner>
|
||||
<RowBetween padding="1rem">
|
||||
<RowBetween style={{ alignItems: 'flex-start' }} padding="1rem 1rem 0 1rem">
|
||||
<HeaderElement>
|
||||
<Title>
|
||||
<UniIcon id="link" to="/">
|
||||
<Title href=".">
|
||||
<UniIcon>
|
||||
<img src={isDark ? LogoDark : Logo} alt="logo" />
|
||||
</UniIcon>
|
||||
{!isMobile && (
|
||||
<TitleText>
|
||||
<HistoryLink id="link" to="/">
|
||||
<img
|
||||
style={{ marginLeft: '4px', marginTop: '4px' }}
|
||||
src={isDark ? WordmarkDark : Wordmark}
|
||||
alt="logo"
|
||||
/>
|
||||
</HistoryLink>
|
||||
</TitleText>
|
||||
)}
|
||||
<TitleText>
|
||||
<img style={{ marginLeft: '4px', marginTop: '4px' }} src={isDark ? WordmarkDark : Wordmark} alt="logo" />
|
||||
</TitleText>
|
||||
</Title>
|
||||
<TestnetWrapper style={{ pointerEvents: 'auto' }}>{!isMobile && <VersionSwitch />}</TestnetWrapper>
|
||||
</HeaderElement>
|
||||
<HeaderElement>
|
||||
<TestnetWrapper>
|
||||
{!isMobile && NETWORK_LABELS[chainId] && <NetworkCard>{NETWORK_LABELS[chainId]}</NetworkCard>}
|
||||
</TestnetWrapper>
|
||||
<AccountElement active={!!account} style={{ pointerEvents: 'auto' }}>
|
||||
<BalanceWrapper>
|
||||
<HeaderControls>
|
||||
<HeaderElement>
|
||||
<TestnetWrapper>
|
||||
{!isMobile && NETWORK_LABELS[chainId] && <NetworkCard>{NETWORK_LABELS[chainId]}</NetworkCard>}
|
||||
</TestnetWrapper>
|
||||
<AccountElement active={!!account} style={{ pointerEvents: 'auto' }}>
|
||||
{account && userEthBalance ? (
|
||||
<Text style={{ flexShrink: 0 }} pl="0.75rem" pr="0.5rem" fontWeight={500}>
|
||||
<BalanceText style={{ flexShrink: 0 }} pl="0.75rem" pr="0.5rem" fontWeight={500}>
|
||||
{userEthBalance?.toSignificant(4)} ETH
|
||||
</Text>
|
||||
</BalanceText>
|
||||
) : null}
|
||||
</BalanceWrapper>
|
||||
<Web3Status />
|
||||
</AccountElement>
|
||||
<Settings />
|
||||
<Menu />
|
||||
</HeaderElement>
|
||||
<Web3Status />
|
||||
</AccountElement>
|
||||
</HeaderElement>
|
||||
<HeaderElementWrap>
|
||||
<VersionSwitch />
|
||||
<Settings />
|
||||
<Menu />
|
||||
</HeaderElementWrap>
|
||||
</HeaderControls>
|
||||
</RowBetween>
|
||||
</HeaderFrame>
|
||||
)
|
||||
|
||||
@@ -77,7 +77,7 @@ const MenuItem = styled(ExternalLink)`
|
||||
}
|
||||
`
|
||||
|
||||
const CODE_LINK = 'https://github.com/Uniswap/uniswap-frontend'
|
||||
const CODE_LINK = 'https://github.com/Uniswap/uniswap-interface'
|
||||
|
||||
export default function Menu() {
|
||||
const node = useRef<HTMLDivElement>()
|
||||
@@ -121,7 +121,7 @@ export default function Menu() {
|
||||
<Code size={14} />
|
||||
Code
|
||||
</MenuItem>
|
||||
<MenuItem id="link" href="https://discord.gg/vXCdddD">
|
||||
<MenuItem id="link" href="https://discord.gg/EwFs3Pp">
|
||||
<MessageCircle size={14} />
|
||||
Discord
|
||||
</MenuItem>
|
||||
|
||||
@@ -1,37 +1,18 @@
|
||||
import React, { useCallback } from 'react'
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { darken } from 'polished'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { withRouter, NavLink, Link as HistoryLink, RouteComponentProps } from 'react-router-dom'
|
||||
import useBodyKeyDown from '../../hooks/useBodyKeyDown'
|
||||
import { NavLink, Link as HistoryLink } from 'react-router-dom'
|
||||
|
||||
import { CursorPointer } from '../../theme'
|
||||
import { ArrowLeft } from 'react-feather'
|
||||
import { RowBetween } from '../Row'
|
||||
import QuestionHelper from '../QuestionHelper'
|
||||
|
||||
const tabOrder = [
|
||||
{
|
||||
path: '/swap',
|
||||
textKey: 'swap',
|
||||
regex: /\/swap/
|
||||
},
|
||||
{
|
||||
path: '/send',
|
||||
textKey: 'send',
|
||||
regex: /\/send/
|
||||
},
|
||||
{
|
||||
path: '/pool',
|
||||
textKey: 'pool',
|
||||
regex: /\/pool/
|
||||
}
|
||||
]
|
||||
|
||||
const Tabs = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
align-items: center;
|
||||
border-radius: 3rem;
|
||||
justify-content: space-evenly;
|
||||
`
|
||||
|
||||
const activeClassName = 'ACTIVE'
|
||||
@@ -43,7 +24,6 @@ const StyledNavLink = styled(NavLink).attrs({
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 3rem;
|
||||
flex: 1 0 auto;
|
||||
border-radius: 3rem;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
@@ -68,89 +48,54 @@ const ActiveText = styled.div`
|
||||
font-size: 20px;
|
||||
`
|
||||
|
||||
const ArrowLink = styled(ArrowLeft)`
|
||||
const StyledArrowLeft = styled(ArrowLeft)`
|
||||
color: ${({ theme }) => theme.text1};
|
||||
`
|
||||
|
||||
function NavigationTabs({ location: { pathname }, history }: RouteComponentProps<{}>) {
|
||||
export function SwapPoolTabs({ active }: { active: 'swap' | 'pool' }) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const navigate = useCallback(
|
||||
direction => {
|
||||
const tabIndex = tabOrder.findIndex(({ regex }) => pathname.match(regex))
|
||||
history.push(tabOrder[(tabIndex + tabOrder.length + direction) % tabOrder.length].path)
|
||||
},
|
||||
[pathname, history]
|
||||
)
|
||||
const navigateRight = useCallback(() => {
|
||||
navigate(1)
|
||||
}, [navigate])
|
||||
const navigateLeft = useCallback(() => {
|
||||
navigate(-1)
|
||||
}, [navigate])
|
||||
|
||||
useBodyKeyDown('ArrowRight', navigateRight)
|
||||
useBodyKeyDown('ArrowLeft', navigateLeft)
|
||||
|
||||
const adding = pathname.match('/add')
|
||||
const removing = pathname.match('/remove')
|
||||
const finding = pathname.match('/find')
|
||||
const creating = pathname.match('/create')
|
||||
|
||||
return (
|
||||
<>
|
||||
{adding || removing ? (
|
||||
<Tabs>
|
||||
<RowBetween style={{ padding: '1rem' }}>
|
||||
<CursorPointer onClick={() => history.push('/pool')}>
|
||||
<ArrowLink />
|
||||
</CursorPointer>
|
||||
<ActiveText>{adding ? 'Add' : 'Remove'} Liquidity</ActiveText>
|
||||
<QuestionHelper
|
||||
text={
|
||||
adding
|
||||
? 'When you add liquidity, you are given pool tokens representing your position. These tokens automatically earn fees proportional to your share of the pool, and can be redeemed at any time.'
|
||||
: 'Removing pool tokens converts your position back into underlying tokens at the current rate, proportional to your share of the pool. Accrued fees are included in the amounts you receive.'
|
||||
}
|
||||
/>
|
||||
</RowBetween>
|
||||
</Tabs>
|
||||
) : finding ? (
|
||||
<Tabs>
|
||||
<RowBetween style={{ padding: '1rem' }}>
|
||||
<HistoryLink to="/pool">
|
||||
<ArrowLink />
|
||||
</HistoryLink>
|
||||
<ActiveText>Import Pool</ActiveText>
|
||||
<QuestionHelper text={"Use this tool to find pairs that don't automatically appear in the interface."} />
|
||||
</RowBetween>
|
||||
</Tabs>
|
||||
) : creating ? (
|
||||
<Tabs>
|
||||
<RowBetween style={{ padding: '1rem' }}>
|
||||
<HistoryLink to="/pool">
|
||||
<ArrowLink />
|
||||
</HistoryLink>
|
||||
<ActiveText>Create Pool</ActiveText>
|
||||
<QuestionHelper text={'Use this interface to create a new pool.'} />
|
||||
</RowBetween>
|
||||
</Tabs>
|
||||
) : (
|
||||
<Tabs style={{ marginBottom: '20px' }}>
|
||||
{tabOrder.map(({ path, textKey, regex }) => (
|
||||
<StyledNavLink
|
||||
id={`${textKey}-nav-link`}
|
||||
key={path}
|
||||
to={path}
|
||||
isActive={(_, { pathname }) => !!pathname.match(regex)}
|
||||
>
|
||||
{t(textKey)}
|
||||
</StyledNavLink>
|
||||
))}
|
||||
</Tabs>
|
||||
)}
|
||||
</>
|
||||
<Tabs style={{ marginBottom: '20px' }}>
|
||||
<StyledNavLink id={`swap-nav-link`} to={'/swap'} isActive={() => active === 'swap'}>
|
||||
{t('swap')}
|
||||
</StyledNavLink>
|
||||
<StyledNavLink id={`pool-nav-link`} to={'/pool'} isActive={() => active === 'pool'}>
|
||||
{t('pool')}
|
||||
</StyledNavLink>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
|
||||
export default withRouter(NavigationTabs)
|
||||
export function FindPoolTabs() {
|
||||
return (
|
||||
<Tabs>
|
||||
<RowBetween style={{ padding: '1rem' }}>
|
||||
<HistoryLink to="/pool">
|
||||
<StyledArrowLeft />
|
||||
</HistoryLink>
|
||||
<ActiveText>Import Pool</ActiveText>
|
||||
<QuestionHelper text={"Use this tool to find pairs that don't automatically appear in the interface."} />
|
||||
</RowBetween>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
|
||||
export function AddRemoveTabs({ adding }: { adding: boolean }) {
|
||||
return (
|
||||
<Tabs>
|
||||
<RowBetween style={{ padding: '1rem' }}>
|
||||
<HistoryLink to="/pool">
|
||||
<StyledArrowLeft />
|
||||
</HistoryLink>
|
||||
<ActiveText>{adding ? 'Add' : 'Remove'} Liquidity</ActiveText>
|
||||
<QuestionHelper
|
||||
text={
|
||||
adding
|
||||
? 'When you add liquidity, you are given pool tokens representing your position. These tokens automatically earn fees proportional to your share of the pool, and can be redeemed at any time.'
|
||||
: 'Removing pool tokens converts your position back into underlying tokens at the current rate, proportional to your share of the pool. Accrued fees are included in the amounts you receive.'
|
||||
}
|
||||
/>
|
||||
</RowBetween>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
|
||||
72
src/components/Popups/ListUpdatePopup.tsx
Normal file
72
src/components/Popups/ListUpdatePopup.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import { TokenList, Version } from '@uniswap/token-lists'
|
||||
import React, { useCallback, useContext } from 'react'
|
||||
import { AlertCircle, Info } from 'react-feather'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { AppDispatch } from '../../state'
|
||||
import { useRemovePopup } from '../../state/application/hooks'
|
||||
import { acceptListUpdate } from '../../state/lists/actions'
|
||||
import { TYPE } from '../../theme'
|
||||
import { ButtonPrimary, ButtonSecondary } from '../Button'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { AutoRow } from '../Row'
|
||||
|
||||
function versionLabel(version: Version): string {
|
||||
return `v${version.major}.${version.minor}.${version.patch}`
|
||||
}
|
||||
|
||||
export default function ListUpdatePopup({
|
||||
popKey,
|
||||
listUrl,
|
||||
oldList,
|
||||
newList,
|
||||
auto
|
||||
}: {
|
||||
popKey: string
|
||||
listUrl: string
|
||||
oldList: TokenList
|
||||
newList: TokenList
|
||||
auto: boolean
|
||||
}) {
|
||||
const removePopup = useRemovePopup()
|
||||
const removeThisPopup = useCallback(() => removePopup(popKey), [popKey, removePopup])
|
||||
const dispatch = useDispatch<AppDispatch>()
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
const updateList = useCallback(() => {
|
||||
if (auto) return
|
||||
dispatch(acceptListUpdate(listUrl))
|
||||
removeThisPopup()
|
||||
}, [auto, dispatch, listUrl, removeThisPopup])
|
||||
|
||||
return (
|
||||
<AutoRow>
|
||||
<div style={{ paddingRight: 16 }}>
|
||||
{auto ? <Info color={theme.text2} size={24} /> : <AlertCircle color={theme.red1} size={24} />}{' '}
|
||||
</div>
|
||||
<AutoColumn style={{ flex: '1' }} gap="8px">
|
||||
{auto ? (
|
||||
<TYPE.body fontWeight={500}>
|
||||
The token list "{oldList.name}" has been updated to{' '}
|
||||
<strong>{versionLabel(newList.version)}</strong>.
|
||||
</TYPE.body>
|
||||
) : (
|
||||
<>
|
||||
<div>
|
||||
A token list update is available for the list "{oldList.name}" ({versionLabel(oldList.version)}{' '}
|
||||
to {versionLabel(newList.version)}).
|
||||
</div>
|
||||
<AutoRow>
|
||||
<div style={{ flexGrow: 1, marginRight: 6 }}>
|
||||
<ButtonPrimary onClick={updateList}>Update list</ButtonPrimary>
|
||||
</div>
|
||||
<div style={{ flexGrow: 1 }}>
|
||||
<ButtonSecondary onClick={removeThisPopup}>Dismiss</ButtonSecondary>
|
||||
</div>
|
||||
</AutoRow>
|
||||
</>
|
||||
)}
|
||||
</AutoColumn>
|
||||
</AutoRow>
|
||||
)
|
||||
}
|
||||
86
src/components/Popups/PopupItem.tsx
Normal file
86
src/components/Popups/PopupItem.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import React, { useCallback, useContext, useState } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
import useInterval from '../../hooks/useInterval'
|
||||
import { PopupContent } from '../../state/application/actions'
|
||||
import { useRemovePopup } from '../../state/application/hooks'
|
||||
import ListUpdatePopup from './ListUpdatePopup'
|
||||
import TxnPopup from './TxnPopup'
|
||||
|
||||
export const StyledClose = styled(X)`
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
export const Popup = styled.div`
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
padding: 1em;
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
padding-right: 35px;
|
||||
z-index: 2;
|
||||
overflow: hidden;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
min-width: 290px;
|
||||
`}
|
||||
`
|
||||
const DELAY = 100
|
||||
const Fader = styled.div<{ count: number }>`
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
width: ${({ count }) => `calc(100% - (100% / ${150 / count}))`};
|
||||
height: 2px;
|
||||
background-color: ${({ theme }) => theme.bg3};
|
||||
transition: width 100ms linear;
|
||||
`
|
||||
|
||||
export default function PopupItem({ content, popKey }: { content: PopupContent; popKey: string }) {
|
||||
const [count, setCount] = useState(1)
|
||||
|
||||
const [isRunning, setIsRunning] = useState(true)
|
||||
const removePopup = useRemovePopup()
|
||||
|
||||
const removeThisPopup = useCallback(() => removePopup(popKey), [popKey, removePopup])
|
||||
|
||||
useInterval(
|
||||
() => {
|
||||
count > 150 ? removeThisPopup() : setCount(count + 1)
|
||||
},
|
||||
isRunning ? DELAY : null
|
||||
)
|
||||
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
const handleMouseEnter = useCallback(() => setIsRunning(false), [])
|
||||
const handleMouseLeave = useCallback(() => setIsRunning(true), [])
|
||||
|
||||
let popupContent
|
||||
if ('txn' in content) {
|
||||
const {
|
||||
txn: { hash, success, summary }
|
||||
} = content
|
||||
popupContent = <TxnPopup hash={hash} success={success} summary={summary} />
|
||||
} else if ('listUpdate' in content) {
|
||||
const {
|
||||
listUpdate: { listUrl, oldList, newList, auto }
|
||||
} = content
|
||||
popupContent = <ListUpdatePopup popKey={popKey} listUrl={listUrl} oldList={oldList} newList={newList} auto={auto} />
|
||||
}
|
||||
|
||||
return (
|
||||
<Popup onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
|
||||
<StyledClose color={theme.text2} onClick={() => removePopup(popKey)} />
|
||||
{popupContent}
|
||||
<Fader count={count} />
|
||||
</Popup>
|
||||
)
|
||||
}
|
||||
27
src/components/Popups/TxnPopup.tsx
Normal file
27
src/components/Popups/TxnPopup.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React, { useContext } from 'react'
|
||||
import { AlertCircle, CheckCircle } from 'react-feather'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { TYPE } from '../../theme'
|
||||
import { ExternalLink } from '../../theme/components'
|
||||
import { getEtherscanLink } from '../../utils'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { AutoRow } from '../Row'
|
||||
|
||||
export default function TxnPopup({ hash, success, summary }: { hash: string; success?: boolean; summary?: string }) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
return (
|
||||
<AutoRow>
|
||||
<div style={{ paddingRight: 16 }}>
|
||||
{success ? <CheckCircle color={theme.green1} size={24} /> : <AlertCircle color={theme.red1} size={24} />}
|
||||
</div>
|
||||
<AutoColumn gap="8px">
|
||||
<TYPE.body fontWeight={500}>{summary ?? 'Hash: ' + hash.slice(0, 8) + '...' + hash.slice(58, 65)}</TYPE.body>
|
||||
<ExternalLink href={getEtherscanLink(chainId, hash, 'transaction')}>View on Etherscan</ExternalLink>
|
||||
</AutoColumn>
|
||||
</AutoRow>
|
||||
)
|
||||
}
|
||||
@@ -1,27 +1,9 @@
|
||||
import { ChainId, Pair, Token } from '@uniswap/sdk'
|
||||
import React, { useContext, useMemo } from 'react'
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { useMediaLayout } from 'use-media'
|
||||
|
||||
import { X } from 'react-feather'
|
||||
import { PopupContent } from '../../state/application/actions'
|
||||
import { useActivePopups, useRemovePopup } from '../../state/application/hooks'
|
||||
import { ExternalLink } from '../../theme'
|
||||
import { useActivePopups } from '../../state/application/hooks'
|
||||
import { AutoColumn } from '../Column'
|
||||
import DoubleTokenLogo from '../DoubleLogo'
|
||||
import Row from '../Row'
|
||||
import TxnPopup from '../TxnPopup'
|
||||
import { Text } from 'rebass'
|
||||
|
||||
const StyledClose = styled(X)`
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
import PopupItem from './PopupItem'
|
||||
|
||||
const MobilePopupWrapper = styled.div<{ height: string | number }>`
|
||||
position: relative;
|
||||
@@ -55,77 +37,9 @@ const FixedPopupColumn = styled(AutoColumn)`
|
||||
`};
|
||||
`
|
||||
|
||||
const Popup = styled.div`
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
padding: 1em;
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
padding-right: 35px;
|
||||
z-index: 2;
|
||||
overflow: hidden;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
min-width: 290px;
|
||||
`}
|
||||
`
|
||||
|
||||
function PoolPopup({
|
||||
token0,
|
||||
token1
|
||||
}: {
|
||||
token0: { address?: string; symbol?: string }
|
||||
token1: { address?: string; symbol?: string }
|
||||
}) {
|
||||
const pairAddress: string | null = useMemo(() => {
|
||||
if (!token0 || !token1) return null
|
||||
// just mock it out
|
||||
return Pair.getAddress(
|
||||
new Token(ChainId.MAINNET, token0.address, 18),
|
||||
new Token(ChainId.MAINNET, token1.address, 18)
|
||||
)
|
||||
}, [token0, token1])
|
||||
|
||||
return (
|
||||
<AutoColumn gap={'10px'}>
|
||||
<Text fontSize={20} fontWeight={500}>
|
||||
Pool Imported
|
||||
</Text>
|
||||
<Row>
|
||||
<DoubleTokenLogo a0={token0?.address ?? ''} a1={token1?.address ?? ''} margin={true} />
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
UNI {token0?.symbol} / {token1?.symbol}
|
||||
</Text>
|
||||
</Row>
|
||||
{pairAddress ? (
|
||||
<ExternalLink href={`https://uniswap.info/pair/${pairAddress}`}>View on Uniswap Info.</ExternalLink>
|
||||
) : null}
|
||||
</AutoColumn>
|
||||
)
|
||||
}
|
||||
|
||||
function PopupItem({ content, popKey }: { content: PopupContent; popKey: string }) {
|
||||
if ('txn' in content) {
|
||||
const {
|
||||
txn: { hash, success, summary }
|
||||
} = content
|
||||
return <TxnPopup popKey={popKey} hash={hash} success={success} summary={summary} />
|
||||
} else if ('poolAdded' in content) {
|
||||
const {
|
||||
poolAdded: { token0, token1 }
|
||||
} = content
|
||||
|
||||
return <PoolPopup token0={token0} token1={token1} />
|
||||
}
|
||||
}
|
||||
|
||||
export default function Popups() {
|
||||
const theme = useContext(ThemeContext)
|
||||
// get all popups
|
||||
const activePopups = useActivePopups()
|
||||
const removePopup = useRemovePopup()
|
||||
|
||||
// switch view settings on mobile
|
||||
const isMobile = useMediaLayout({ maxWidth: '600px' })
|
||||
@@ -133,14 +47,9 @@ export default function Popups() {
|
||||
if (!isMobile) {
|
||||
return (
|
||||
<FixedPopupColumn gap="20px">
|
||||
{activePopups.map(item => {
|
||||
return (
|
||||
<Popup key={item.key}>
|
||||
<StyledClose color={theme.text2} onClick={() => removePopup(item.key)} />
|
||||
<PopupItem content={item.content} popKey={item.key} />
|
||||
</Popup>
|
||||
)
|
||||
})}
|
||||
{activePopups.map(item => (
|
||||
<PopupItem key={item.key} content={item.content} popKey={item.key} />
|
||||
))}
|
||||
</FixedPopupColumn>
|
||||
)
|
||||
}
|
||||
@@ -152,14 +61,9 @@ export default function Popups() {
|
||||
{activePopups // reverse so new items up front
|
||||
.slice(0)
|
||||
.reverse()
|
||||
.map(item => {
|
||||
return (
|
||||
<Popup key={item.key}>
|
||||
<StyledClose color={theme.text2} onClick={() => removePopup(item.key)} />
|
||||
<PopupItem content={item.content} popKey={item.key} />
|
||||
</Popup>
|
||||
)
|
||||
})}
|
||||
.map(item => (
|
||||
<PopupItem key={item.key} content={item.content} popKey={item.key} />
|
||||
))}
|
||||
</MobilePopupInner>
|
||||
</MobilePopupWrapper>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useContext } from 'react'
|
||||
import { RouteComponentProps, withRouter } from 'react-router-dom'
|
||||
import { Link, RouteComponentProps, withRouter } from 'react-router-dom'
|
||||
import { Token, TokenAmount, WETH } from '@uniswap/sdk'
|
||||
|
||||
import { Text } from 'rebass'
|
||||
@@ -7,7 +7,7 @@ import { AutoColumn } from '../Column'
|
||||
import { ButtonSecondary } from '../Button'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
import { FixedHeightRow, HoverCard } from './index'
|
||||
import DoubleTokenLogo from '../DoubleLogo'
|
||||
import DoubleCurrencyLogo from '../DoubleLogo'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
|
||||
@@ -16,7 +16,7 @@ interface PositionCardProps extends RouteComponentProps<{}> {
|
||||
V1LiquidityBalance: TokenAmount
|
||||
}
|
||||
|
||||
function V1PositionCard({ token, V1LiquidityBalance, history }: PositionCardProps) {
|
||||
function V1PositionCard({ token, V1LiquidityBalance }: PositionCardProps) {
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
const { chainId } = useActiveWeb3React()
|
||||
@@ -26,7 +26,7 @@ function V1PositionCard({ token, V1LiquidityBalance, history }: PositionCardProp
|
||||
<AutoColumn gap="12px">
|
||||
<FixedHeightRow>
|
||||
<RowFixed>
|
||||
<DoubleTokenLogo a0={token.address} margin={true} size={20} />
|
||||
<DoubleCurrencyLogo currency0={token} margin={true} size={20} />
|
||||
<Text fontWeight={500} fontSize={20} style={{ marginLeft: '' }}>
|
||||
{`${token.equals(WETH[chainId]) ? 'WETH' : token.symbol}/ETH`}
|
||||
</Text>
|
||||
@@ -47,21 +47,15 @@ function V1PositionCard({ token, V1LiquidityBalance, history }: PositionCardProp
|
||||
|
||||
<AutoColumn gap="8px">
|
||||
<RowBetween marginTop="10px">
|
||||
<ButtonSecondary
|
||||
width="68%"
|
||||
onClick={() => {
|
||||
history.push(`/migrate/v1/${V1LiquidityBalance.token.address}`)
|
||||
}}
|
||||
>
|
||||
<ButtonSecondary width="68%" as={Link} to={`/migrate/v1/${V1LiquidityBalance.token.address}`}>
|
||||
Migrate
|
||||
</ButtonSecondary>
|
||||
|
||||
<ButtonSecondary
|
||||
style={{ backgroundColor: 'transparent' }}
|
||||
width="28%"
|
||||
onClick={() => {
|
||||
history.push(`/remove/v1/${V1LiquidityBalance.token.address}`)
|
||||
}}
|
||||
as={Link}
|
||||
to={`/remove/v1/${V1LiquidityBalance.token.address}`}
|
||||
>
|
||||
Remove
|
||||
</ButtonSecondary>
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import React, { useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { JSBI, Pair, Percent } from '@uniswap/sdk'
|
||||
import { darken } from 'polished'
|
||||
import { RouteComponentProps, withRouter } from 'react-router-dom'
|
||||
import { Percent, Pair, JSBI } from '@uniswap/sdk'
|
||||
import React, { useState } from 'react'
|
||||
import { ChevronDown, ChevronUp } from 'react-feather'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Text } from 'rebass'
|
||||
import styled from 'styled-components'
|
||||
import { useTotalSupply } from '../../data/TotalSupply'
|
||||
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useTotalSupply } from '../../data/TotalSupply'
|
||||
import { useTokenBalance } from '../../state/wallet/hooks'
|
||||
import { ExternalLink } from '../../theme'
|
||||
import { currencyId } from '../../utils/currencyId'
|
||||
import { unwrappedToken } from '../../utils/wrappedCurrency'
|
||||
import { ButtonSecondary } from '../Button'
|
||||
|
||||
import Card, { GreyCard } from '../Card'
|
||||
import TokenLogo from '../TokenLogo'
|
||||
import DoubleLogo from '../DoubleLogo'
|
||||
import { Text } from 'rebass'
|
||||
import { ExternalLink } from '../../theme/components'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { ChevronDown, ChevronUp } from 'react-feather'
|
||||
import { ButtonSecondary } from '../Button'
|
||||
import { RowBetween, RowFixed, AutoRow } from '../Row'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import DoubleCurrencyLogo from '../DoubleLogo'
|
||||
import { AutoRow, RowBetween, RowFixed } from '../Row'
|
||||
import { Dots } from '../swap/styleds'
|
||||
|
||||
export const FixedHeightRow = styled(RowBetween)`
|
||||
@@ -30,22 +32,107 @@ export const HoverCard = styled(Card)`
|
||||
}
|
||||
`
|
||||
|
||||
interface PositionCardProps extends RouteComponentProps<{}> {
|
||||
interface PositionCardProps {
|
||||
pair: Pair
|
||||
minimal?: boolean
|
||||
showUnwrapped?: boolean
|
||||
border?: string
|
||||
}
|
||||
|
||||
function PositionCard({ pair, history, border, minimal = false }: PositionCardProps) {
|
||||
export function MinimalPositionCard({ pair, showUnwrapped = false, border }: PositionCardProps) {
|
||||
const { account } = useActiveWeb3React()
|
||||
|
||||
const token0 = pair?.token0
|
||||
const token1 = pair?.token1
|
||||
const currency0 = showUnwrapped ? pair.token0 : unwrappedToken(pair.token0)
|
||||
const currency1 = showUnwrapped ? pair.token1 : unwrappedToken(pair.token1)
|
||||
|
||||
const [showMore, setShowMore] = useState(false)
|
||||
|
||||
const userPoolBalance = useTokenBalance(account, pair?.liquidityToken)
|
||||
const totalPoolTokens = useTotalSupply(pair?.liquidityToken)
|
||||
const userPoolBalance = useTokenBalance(account, pair.liquidityToken)
|
||||
const totalPoolTokens = useTotalSupply(pair.liquidityToken)
|
||||
|
||||
const [token0Deposited, token1Deposited] =
|
||||
!!pair &&
|
||||
!!totalPoolTokens &&
|
||||
!!userPoolBalance &&
|
||||
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply
|
||||
JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
|
||||
? [
|
||||
pair.getLiquidityValue(pair.token0, totalPoolTokens, userPoolBalance, false),
|
||||
pair.getLiquidityValue(pair.token1, totalPoolTokens, userPoolBalance, false)
|
||||
]
|
||||
: [undefined, undefined]
|
||||
|
||||
return (
|
||||
<>
|
||||
{userPoolBalance && (
|
||||
<GreyCard border={border}>
|
||||
<AutoColumn gap="12px">
|
||||
<FixedHeightRow>
|
||||
<RowFixed>
|
||||
<Text fontWeight={500} fontSize={16}>
|
||||
Your position
|
||||
</Text>
|
||||
</RowFixed>
|
||||
</FixedHeightRow>
|
||||
<FixedHeightRow onClick={() => setShowMore(!showMore)}>
|
||||
<RowFixed>
|
||||
<DoubleCurrencyLogo currency0={currency0} currency1={currency1} margin={true} size={20} />
|
||||
<Text fontWeight={500} fontSize={20}>
|
||||
{currency0.symbol}/{currency1.symbol}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
<RowFixed>
|
||||
<Text fontWeight={500} fontSize={20}>
|
||||
{userPoolBalance ? userPoolBalance.toSignificant(4) : '-'}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
</FixedHeightRow>
|
||||
<AutoColumn gap="4px">
|
||||
<FixedHeightRow>
|
||||
<Text color="#888D9B" fontSize={16} fontWeight={500}>
|
||||
{currency0.symbol}:
|
||||
</Text>
|
||||
{token0Deposited ? (
|
||||
<RowFixed>
|
||||
<Text color="#888D9B" fontSize={16} fontWeight={500} marginLeft={'6px'}>
|
||||
{token0Deposited?.toSignificant(6)}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</FixedHeightRow>
|
||||
<FixedHeightRow>
|
||||
<Text color="#888D9B" fontSize={16} fontWeight={500}>
|
||||
{currency1.symbol}:
|
||||
</Text>
|
||||
{token1Deposited ? (
|
||||
<RowFixed>
|
||||
<Text color="#888D9B" fontSize={16} fontWeight={500} marginLeft={'6px'}>
|
||||
{token1Deposited?.toSignificant(6)}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</FixedHeightRow>
|
||||
</AutoColumn>
|
||||
</AutoColumn>
|
||||
</GreyCard>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default function FullPositionCard({ pair, border }: PositionCardProps) {
|
||||
const { account } = useActiveWeb3React()
|
||||
|
||||
const currency0 = unwrappedToken(pair.token0)
|
||||
const currency1 = unwrappedToken(pair.token1)
|
||||
|
||||
const [showMore, setShowMore] = useState(false)
|
||||
|
||||
const userPoolBalance = useTokenBalance(account, pair.liquidityToken)
|
||||
const totalPoolTokens = useTotalSupply(pair.liquidityToken)
|
||||
|
||||
const poolTokenPercentage =
|
||||
!!userPoolBalance && !!totalPoolTokens && JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
|
||||
@@ -59,173 +146,99 @@ function PositionCard({ pair, history, border, minimal = false }: PositionCardPr
|
||||
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply
|
||||
JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
|
||||
? [
|
||||
pair.getLiquidityValue(token0, totalPoolTokens, userPoolBalance, false),
|
||||
pair.getLiquidityValue(token1, totalPoolTokens, userPoolBalance, false)
|
||||
pair.getLiquidityValue(pair.token0, totalPoolTokens, userPoolBalance, false),
|
||||
pair.getLiquidityValue(pair.token1, totalPoolTokens, userPoolBalance, false)
|
||||
]
|
||||
: [undefined, undefined]
|
||||
|
||||
if (minimal) {
|
||||
return (
|
||||
<>
|
||||
{userPoolBalance && (
|
||||
<GreyCard border={border}>
|
||||
<AutoColumn gap="12px">
|
||||
<FixedHeightRow>
|
||||
return (
|
||||
<HoverCard border={border}>
|
||||
<AutoColumn gap="12px">
|
||||
<FixedHeightRow onClick={() => setShowMore(!showMore)} style={{ cursor: 'pointer' }}>
|
||||
<RowFixed>
|
||||
<DoubleCurrencyLogo currency0={currency0} currency1={currency1} margin={true} size={20} />
|
||||
<Text fontWeight={500} fontSize={20}>
|
||||
{!currency0 || !currency1 ? <Dots>Loading</Dots> : `${currency0.symbol}/${currency1.symbol}`}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
<RowFixed>
|
||||
{showMore ? (
|
||||
<ChevronUp size="20" style={{ marginLeft: '10px' }} />
|
||||
) : (
|
||||
<ChevronDown size="20" style={{ marginLeft: '10px' }} />
|
||||
)}
|
||||
</RowFixed>
|
||||
</FixedHeightRow>
|
||||
{showMore && (
|
||||
<AutoColumn gap="8px">
|
||||
<FixedHeightRow>
|
||||
<RowFixed>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
Pooled {currency0.symbol}:
|
||||
</Text>
|
||||
</RowFixed>
|
||||
{token0Deposited ? (
|
||||
<RowFixed>
|
||||
<Text fontWeight={500} fontSize={16}>
|
||||
Your position
|
||||
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
|
||||
{token0Deposited?.toSignificant(6)}
|
||||
</Text>
|
||||
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={currency0} />
|
||||
</RowFixed>
|
||||
</FixedHeightRow>
|
||||
<FixedHeightRow onClick={() => setShowMore(!showMore)}>
|
||||
<RowFixed>
|
||||
<DoubleLogo a0={token0?.address || ''} a1={token1?.address || ''} margin={true} size={20} />
|
||||
<Text fontWeight={500} fontSize={20}>
|
||||
{token0?.symbol}/{token1?.symbol}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
<RowFixed>
|
||||
<Text fontWeight={500} fontSize={20}>
|
||||
{userPoolBalance ? userPoolBalance.toSignificant(4) : '-'}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
</FixedHeightRow>
|
||||
<AutoColumn gap="4px">
|
||||
<FixedHeightRow>
|
||||
<Text color="#888D9B" fontSize={16} fontWeight={500}>
|
||||
{token0?.symbol}:
|
||||
</Text>
|
||||
{token0Deposited ? (
|
||||
<RowFixed>
|
||||
<Text color="#888D9B" fontSize={16} fontWeight={500} marginLeft={'6px'}>
|
||||
{token0Deposited?.toSignificant(6)}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</FixedHeightRow>
|
||||
<FixedHeightRow>
|
||||
<Text color="#888D9B" fontSize={16} fontWeight={500}>
|
||||
{token1?.symbol}:
|
||||
</Text>
|
||||
{token1Deposited ? (
|
||||
<RowFixed>
|
||||
<Text color="#888D9B" fontSize={16} fontWeight={500} marginLeft={'6px'}>
|
||||
{token1Deposited?.toSignificant(6)}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</FixedHeightRow>
|
||||
</AutoColumn>
|
||||
</AutoColumn>
|
||||
</GreyCard>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
} else
|
||||
return (
|
||||
<HoverCard border={border}>
|
||||
<AutoColumn gap="12px">
|
||||
<FixedHeightRow onClick={() => setShowMore(!showMore)} style={{ cursor: 'pointer' }}>
|
||||
<RowFixed>
|
||||
<DoubleLogo a0={token0?.address || ''} a1={token1?.address || ''} margin={true} size={20} />
|
||||
<Text fontWeight={500} fontSize={20}>
|
||||
{!token0 || !token1 ? <Dots>Loading</Dots> : `${token0.symbol}/${token1.symbol}`}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
<RowFixed>
|
||||
{showMore ? (
|
||||
<ChevronUp size="20" style={{ marginLeft: '10px' }} />
|
||||
) : (
|
||||
<ChevronDown size="20" style={{ marginLeft: '10px' }} />
|
||||
'-'
|
||||
)}
|
||||
</RowFixed>
|
||||
</FixedHeightRow>
|
||||
{showMore && (
|
||||
<AutoColumn gap="8px">
|
||||
<FixedHeightRow>
|
||||
<RowFixed>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
Pooled {token0?.symbol}:
|
||||
</Text>
|
||||
</RowFixed>
|
||||
{token0Deposited ? (
|
||||
<RowFixed>
|
||||
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
|
||||
{token0Deposited?.toSignificant(6)}
|
||||
</Text>
|
||||
<TokenLogo size="20px" style={{ marginLeft: '8px' }} address={token0?.address} />
|
||||
</RowFixed>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</FixedHeightRow>
|
||||
</FixedHeightRow>
|
||||
|
||||
<FixedHeightRow>
|
||||
<FixedHeightRow>
|
||||
<RowFixed>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
Pooled {currency1.symbol}:
|
||||
</Text>
|
||||
</RowFixed>
|
||||
{token1Deposited ? (
|
||||
<RowFixed>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
Pooled {token1?.symbol}:
|
||||
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
|
||||
{token1Deposited?.toSignificant(6)}
|
||||
</Text>
|
||||
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={currency1} />
|
||||
</RowFixed>
|
||||
{token1Deposited ? (
|
||||
<RowFixed>
|
||||
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
|
||||
{token1Deposited?.toSignificant(6)}
|
||||
</Text>
|
||||
<TokenLogo size="20px" style={{ marginLeft: '8px' }} address={token1?.address} />
|
||||
</RowFixed>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</FixedHeightRow>
|
||||
<FixedHeightRow>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
Your pool tokens:
|
||||
</Text>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
{userPoolBalance ? userPoolBalance.toSignificant(4) : '-'}
|
||||
</Text>
|
||||
</FixedHeightRow>
|
||||
<FixedHeightRow>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
Your pool share:
|
||||
</Text>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
{poolTokenPercentage ? poolTokenPercentage.toFixed(2) + '%' : '-'}
|
||||
</Text>
|
||||
</FixedHeightRow>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</FixedHeightRow>
|
||||
<FixedHeightRow>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
Your pool tokens:
|
||||
</Text>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
{userPoolBalance ? userPoolBalance.toSignificant(4) : '-'}
|
||||
</Text>
|
||||
</FixedHeightRow>
|
||||
<FixedHeightRow>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
Your pool share:
|
||||
</Text>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
{poolTokenPercentage ? poolTokenPercentage.toFixed(2) + '%' : '-'}
|
||||
</Text>
|
||||
</FixedHeightRow>
|
||||
|
||||
<AutoRow justify="center" marginTop={'10px'}>
|
||||
<ExternalLink href={`https://uniswap.info/pair/${pair?.liquidityToken.address}`}>
|
||||
View pool information ↗
|
||||
</ExternalLink>
|
||||
</AutoRow>
|
||||
<RowBetween marginTop="10px">
|
||||
<ButtonSecondary
|
||||
width="48%"
|
||||
onClick={() => {
|
||||
history.push('/add/' + token0?.address + '-' + token1?.address)
|
||||
}}
|
||||
>
|
||||
Add
|
||||
</ButtonSecondary>
|
||||
<ButtonSecondary
|
||||
width="48%"
|
||||
onClick={() => {
|
||||
history.push('/remove/' + token0?.address + '-' + token1?.address)
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
</ButtonSecondary>
|
||||
</RowBetween>
|
||||
</AutoColumn>
|
||||
)}
|
||||
</AutoColumn>
|
||||
</HoverCard>
|
||||
)
|
||||
<AutoRow justify="center" marginTop={'10px'}>
|
||||
<ExternalLink href={`https://uniswap.info/pair/${pair.liquidityToken.address}`}>
|
||||
View pool information ↗
|
||||
</ExternalLink>
|
||||
</AutoRow>
|
||||
<RowBetween marginTop="10px">
|
||||
<ButtonSecondary as={Link} to={`/add/${currencyId(currency0)}/${currencyId(currency1)}`} width="48%">
|
||||
Add
|
||||
</ButtonSecondary>
|
||||
<ButtonSecondary as={Link} width="48%" to={`/remove/${currencyId(currency0)}/${currencyId(currency1)}`}>
|
||||
Remove
|
||||
</ButtonSecondary>
|
||||
</RowBetween>
|
||||
</AutoColumn>
|
||||
)}
|
||||
</AutoColumn>
|
||||
</HoverCard>
|
||||
)
|
||||
}
|
||||
|
||||
export default withRouter(PositionCard)
|
||||
|
||||
@@ -5,14 +5,13 @@ const Row = styled(Box)<{ align?: string; padding?: string; border?: string; bor
|
||||
width: 100%;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
align-items: center;
|
||||
align-items: ${({ align }) => align && align};
|
||||
align-items: ${({ align }) => (align ? align : 'center')};
|
||||
padding: ${({ padding }) => padding};
|
||||
border: ${({ border }) => border};
|
||||
border-radius: ${({ borderRadius }) => borderRadius};
|
||||
`
|
||||
|
||||
export const RowBetween = styled(Row)<{ align?: string; padding?: string; border?: string; borderRadius?: string }>`
|
||||
export const RowBetween = styled(Row)`
|
||||
justify-content: space-between;
|
||||
`
|
||||
|
||||
|
||||
@@ -1,41 +1,62 @@
|
||||
import React from 'react'
|
||||
import { Text } from 'rebass'
|
||||
import { Token } from '@uniswap/sdk'
|
||||
import { ChainId, Currency, currencyEquals, ETHER, Token } from '@uniswap/sdk'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { SUGGESTED_BASES } from '../../constants'
|
||||
import { AutoColumn } from '../Column'
|
||||
import QuestionHelper from '../QuestionHelper'
|
||||
import { AutoRow } from '../Row'
|
||||
import TokenLogo from '../TokenLogo'
|
||||
import { BaseWrapper } from './styleds'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
|
||||
const BaseWrapper = styled.div<{ disable?: boolean }>`
|
||||
border: 1px solid ${({ theme, disable }) => (disable ? 'transparent' : theme.bg3)};
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
padding: 6px;
|
||||
|
||||
align-items: center;
|
||||
:hover {
|
||||
cursor: ${({ disable }) => !disable && 'pointer'};
|
||||
background-color: ${({ theme, disable }) => !disable && theme.bg2};
|
||||
}
|
||||
|
||||
background-color: ${({ theme, disable }) => disable && theme.bg3};
|
||||
opacity: ${({ disable }) => disable && '0.4'};
|
||||
`
|
||||
|
||||
export default function CommonBases({
|
||||
chainId,
|
||||
onSelect,
|
||||
selectedTokenAddress
|
||||
selectedCurrency
|
||||
}: {
|
||||
chainId: number
|
||||
selectedTokenAddress: string
|
||||
onSelect: (tokenAddress: string) => void
|
||||
chainId?: ChainId
|
||||
selectedCurrency?: Currency
|
||||
onSelect: (currency: Currency) => void
|
||||
}) {
|
||||
return (
|
||||
<AutoColumn gap="md">
|
||||
<AutoRow>
|
||||
<Text fontWeight={500} fontSize={16}>
|
||||
Common Bases
|
||||
<Text fontWeight={500} fontSize={14}>
|
||||
Common bases
|
||||
</Text>
|
||||
<QuestionHelper text="These tokens are commonly used in pairs." />
|
||||
<QuestionHelper text="These tokens are commonly paired with other tokens." />
|
||||
</AutoRow>
|
||||
<AutoRow gap="10px">
|
||||
{(SUGGESTED_BASES[chainId] ?? []).map((token: Token) => {
|
||||
<AutoRow gap="4px">
|
||||
<BaseWrapper
|
||||
onClick={() => !currencyEquals(selectedCurrency, ETHER) && onSelect(ETHER)}
|
||||
disable={selectedCurrency === ETHER}
|
||||
>
|
||||
<CurrencyLogo currency={ETHER} style={{ marginRight: 8 }} />
|
||||
<Text fontWeight={500} fontSize={16}>
|
||||
ETH
|
||||
</Text>
|
||||
</BaseWrapper>
|
||||
{(chainId ? SUGGESTED_BASES[chainId] : []).map((token: Token) => {
|
||||
const selected = selectedCurrency instanceof Token && selectedCurrency.address === token.address
|
||||
return (
|
||||
<BaseWrapper
|
||||
gap="6px"
|
||||
onClick={() => selectedTokenAddress !== token.address && onSelect(token.address)}
|
||||
disable={selectedTokenAddress === token.address}
|
||||
key={token.address}
|
||||
>
|
||||
<TokenLogo address={token.address} />
|
||||
<BaseWrapper onClick={() => !selected && onSelect(token)} disable={selected} key={token.address}>
|
||||
<CurrencyLogo currency={token} style={{ marginRight: 8 }} />
|
||||
<Text fontWeight={500} fontSize={16}>
|
||||
{token.symbol}
|
||||
</Text>
|
||||
|
||||
155
src/components/SearchModal/CurrencyList.tsx
Normal file
155
src/components/SearchModal/CurrencyList.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
import { Currency, CurrencyAmount, currencyEquals, ETHER, JSBI, Token } from '@uniswap/sdk'
|
||||
import React, { CSSProperties, memo, useContext, useMemo } from 'react'
|
||||
import { FixedSizeList } from 'react-window'
|
||||
import { Text } from 'rebass'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useAllTokens } from '../../hooks/Tokens'
|
||||
import { useDefaultTokenList } from '../../state/lists/hooks'
|
||||
import { useAddUserToken, useRemoveUserAddedToken } from '../../state/user/hooks'
|
||||
import { useETHBalances } from '../../state/wallet/hooks'
|
||||
import { LinkStyledButton, TYPE } from '../../theme'
|
||||
import { ButtonSecondary } from '../Button'
|
||||
import Column, { AutoColumn } from '../Column'
|
||||
import { RowFixed } from '../Row'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import { FadedSpan, MenuItem } from './styleds'
|
||||
import Loader from '../Loader'
|
||||
import { isDefaultToken } from '../../utils'
|
||||
|
||||
function currencyKey(currency: Currency): string {
|
||||
return currency instanceof Token ? currency.address : currency === ETHER ? 'ETHER' : ''
|
||||
}
|
||||
|
||||
export default function CurrencyList({
|
||||
currencies,
|
||||
allBalances,
|
||||
selectedCurrency,
|
||||
onCurrencySelect,
|
||||
otherCurrency,
|
||||
showSendWithSwap
|
||||
}: {
|
||||
currencies: Currency[]
|
||||
selectedCurrency: Currency
|
||||
allBalances: { [tokenAddress: string]: CurrencyAmount }
|
||||
onCurrencySelect: (currency: Currency) => void
|
||||
otherCurrency: Currency
|
||||
showSendWithSwap?: boolean
|
||||
}) {
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
const theme = useContext(ThemeContext)
|
||||
const allTokens = useAllTokens()
|
||||
const defaultTokens = useDefaultTokenList()
|
||||
const addToken = useAddUserToken()
|
||||
const removeToken = useRemoveUserAddedToken()
|
||||
const ETHBalance = useETHBalances([account])[account]
|
||||
|
||||
const CurrencyRow = useMemo(() => {
|
||||
return memo(function CurrencyRow({ index, style }: { index: number; style: CSSProperties }) {
|
||||
const currency = index === 0 ? Currency.ETHER : currencies[index - 1]
|
||||
const key = currencyKey(currency)
|
||||
const isDefault = isDefaultToken(defaultTokens, currency)
|
||||
const customAdded = Boolean(!isDefault && currency instanceof Token && allTokens[currency.address])
|
||||
const balance = currency === ETHER ? ETHBalance : allBalances[key]
|
||||
|
||||
const zeroBalance = balance && JSBI.equal(JSBI.BigInt(0), balance.raw)
|
||||
|
||||
const isSelected = Boolean(selectedCurrency && currencyEquals(currency, selectedCurrency))
|
||||
const otherSelected = Boolean(otherCurrency && currencyEquals(otherCurrency, currency))
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
style={style}
|
||||
className={`token-item-${key}`}
|
||||
onClick={() => (isSelected ? null : onCurrencySelect(currency))}
|
||||
disabled={isSelected}
|
||||
selected={otherSelected}
|
||||
>
|
||||
<RowFixed>
|
||||
<CurrencyLogo currency={currency} size={'24px'} style={{ marginRight: '14px' }} />
|
||||
<Column>
|
||||
<Text fontWeight={500}>{currency.symbol}</Text>
|
||||
<FadedSpan>
|
||||
{customAdded ? (
|
||||
<TYPE.main fontWeight={500}>
|
||||
Added by user
|
||||
<LinkStyledButton
|
||||
onClick={event => {
|
||||
event.stopPropagation()
|
||||
if (currency instanceof Token) removeToken(chainId, currency.address)
|
||||
}}
|
||||
>
|
||||
(Remove)
|
||||
</LinkStyledButton>
|
||||
</TYPE.main>
|
||||
) : null}
|
||||
{!isDefault && !customAdded ? (
|
||||
<TYPE.main fontWeight={500}>
|
||||
Found by address
|
||||
<LinkStyledButton
|
||||
onClick={event => {
|
||||
event.stopPropagation()
|
||||
if (currency instanceof Token) addToken(currency)
|
||||
}}
|
||||
>
|
||||
(Add)
|
||||
</LinkStyledButton>
|
||||
</TYPE.main>
|
||||
) : null}
|
||||
</FadedSpan>
|
||||
</Column>
|
||||
</RowFixed>
|
||||
<AutoColumn>
|
||||
{balance ? (
|
||||
<Text>
|
||||
{zeroBalance && showSendWithSwap ? (
|
||||
<ButtonSecondary padding={'4px 8px'}>
|
||||
<Text textAlign="center" fontWeight={500} fontSize={14} color={theme.primary1}>
|
||||
Send With Swap
|
||||
</Text>
|
||||
</ButtonSecondary>
|
||||
) : balance ? (
|
||||
balance.toSignificant(6)
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</Text>
|
||||
) : account ? (
|
||||
<Loader />
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</AutoColumn>
|
||||
</MenuItem>
|
||||
)
|
||||
})
|
||||
}, [
|
||||
ETHBalance,
|
||||
account,
|
||||
addToken,
|
||||
allBalances,
|
||||
allTokens,
|
||||
chainId,
|
||||
currencies,
|
||||
defaultTokens,
|
||||
onCurrencySelect,
|
||||
otherCurrency,
|
||||
removeToken,
|
||||
selectedCurrency,
|
||||
showSendWithSwap,
|
||||
theme.primary1
|
||||
])
|
||||
|
||||
return (
|
||||
<FixedSizeList
|
||||
width="100%"
|
||||
height={500}
|
||||
itemCount={currencies.length + 1}
|
||||
itemSize={56}
|
||||
style={{ flex: '1' }}
|
||||
itemKey={index => currencyKey(currencies[index])}
|
||||
>
|
||||
{CurrencyRow}
|
||||
</FixedSizeList>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Token } from '@uniswap/sdk'
|
||||
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Currency, Token } from '@uniswap/sdk'
|
||||
import React, { KeyboardEvent, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { isMobile } from 'react-device-detect'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Text } from 'rebass'
|
||||
@@ -7,8 +7,9 @@ import { ThemeContext } from 'styled-components'
|
||||
import Card from '../../components/Card'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useAllTokens, useToken } from '../../hooks/Tokens'
|
||||
import { useAllTokenBalancesTreatingWETHasETH, useTokenBalanceTreatingWETHasETH } from '../../state/wallet/hooks'
|
||||
import { CloseIcon, LinkStyledButton } from '../../theme/components'
|
||||
import useInterval from '../../hooks/useInterval'
|
||||
import { useAllTokenBalances, useTokenBalance } from '../../state/wallet/hooks'
|
||||
import { CloseIcon, LinkStyledButton } from '../../theme'
|
||||
import { isAddress } from '../../utils'
|
||||
import Column from '../Column'
|
||||
import Modal from '../Modal'
|
||||
@@ -19,30 +20,28 @@ import CommonBases from './CommonBases'
|
||||
import { filterTokens } from './filtering'
|
||||
import { useTokenComparator } from './sorting'
|
||||
import { PaddedColumn, SearchInput } from './styleds'
|
||||
import TokenList from './TokenList'
|
||||
import CurrencyList from './CurrencyList'
|
||||
import SortButton from './SortButton'
|
||||
|
||||
interface TokenSearchModalProps {
|
||||
interface CurrencySearchModalProps {
|
||||
isOpen?: boolean
|
||||
onDismiss?: () => void
|
||||
hiddenToken?: string
|
||||
hiddenCurrency?: Currency
|
||||
showSendWithSwap?: boolean
|
||||
onTokenSelect?: (address: string) => void
|
||||
otherSelectedTokenAddress?: string
|
||||
otherSelectedText?: string
|
||||
onCurrencySelect?: (currency: Currency) => void
|
||||
otherSelectedCurrency?: Currency
|
||||
showCommonBases?: boolean
|
||||
}
|
||||
|
||||
export default function TokenSearchModal({
|
||||
export default function CurrencySearchModal({
|
||||
isOpen,
|
||||
onDismiss,
|
||||
onTokenSelect,
|
||||
hiddenToken,
|
||||
onCurrencySelect,
|
||||
hiddenCurrency,
|
||||
showSendWithSwap,
|
||||
otherSelectedTokenAddress,
|
||||
otherSelectedText,
|
||||
otherSelectedCurrency,
|
||||
showCommonBases = false
|
||||
}: TokenSearchModalProps) {
|
||||
}: CurrencySearchModalProps) {
|
||||
const { t } = useTranslation()
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
const theme = useContext(ThemeContext)
|
||||
@@ -54,8 +53,8 @@ export default function TokenSearchModal({
|
||||
|
||||
// if the current input is an address, and we don't have the token in context, try to fetch it and import
|
||||
const searchToken = useToken(searchQuery)
|
||||
const searchTokenBalance = useTokenBalanceTreatingWETHasETH(account, searchToken)
|
||||
const allTokenBalances_ = useAllTokenBalancesTreatingWETHasETH()
|
||||
const searchTokenBalance = useTokenBalance(account, searchToken)
|
||||
const allTokenBalances_ = useAllTokenBalances()
|
||||
const allTokenBalances = searchToken
|
||||
? {
|
||||
[searchToken.address]: searchTokenBalance
|
||||
@@ -86,12 +85,12 @@ export default function TokenSearchModal({
|
||||
]
|
||||
}, [filteredTokens, searchQuery, searchToken, tokenComparator])
|
||||
|
||||
const handleTokenSelect = useCallback(
|
||||
(address: string) => {
|
||||
onTokenSelect(address)
|
||||
const handleCurrencySelect = useCallback(
|
||||
(currency: Currency) => {
|
||||
onCurrencySelect(currency)
|
||||
onDismiss()
|
||||
},
|
||||
[onDismiss, onTokenSelect]
|
||||
[onDismiss, onCurrencySelect]
|
||||
)
|
||||
|
||||
// clear the input on open
|
||||
@@ -110,10 +109,31 @@ export default function TokenSearchModal({
|
||||
|
||||
const openTooltip = useCallback(() => {
|
||||
setTooltipOpen(true)
|
||||
inputRef.current?.focus()
|
||||
}, [setTooltipOpen])
|
||||
const closeTooltip = useCallback(() => setTooltipOpen(false), [setTooltipOpen])
|
||||
|
||||
useInterval(
|
||||
() => {
|
||||
setTooltipOpen(false)
|
||||
},
|
||||
tooltipOpen ? 4000 : null,
|
||||
false
|
||||
)
|
||||
|
||||
const handleEnter = useCallback(
|
||||
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter' && filteredSortedTokens.length > 0) {
|
||||
if (
|
||||
filteredSortedTokens[0].symbol.toLowerCase() === searchQuery.trim().toLowerCase() ||
|
||||
filteredSortedTokens.length === 1
|
||||
) {
|
||||
handleCurrencySelect(filteredSortedTokens[0])
|
||||
}
|
||||
}
|
||||
},
|
||||
[filteredSortedTokens, handleCurrencySelect, searchQuery]
|
||||
)
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
@@ -123,7 +143,7 @@ export default function TokenSearchModal({
|
||||
minHeight={70}
|
||||
>
|
||||
<Column style={{ width: '100%' }}>
|
||||
<PaddedColumn gap="20px">
|
||||
<PaddedColumn gap="14px">
|
||||
<RowBetween>
|
||||
<Text fontWeight={500} fontSize={16}>
|
||||
Select a token
|
||||
@@ -146,11 +166,13 @@ export default function TokenSearchModal({
|
||||
value={searchQuery}
|
||||
ref={inputRef}
|
||||
onChange={handleInput}
|
||||
onFocus={closeTooltip}
|
||||
onBlur={closeTooltip}
|
||||
onKeyDown={handleEnter}
|
||||
/>
|
||||
</Tooltip>
|
||||
{showCommonBases && (
|
||||
<CommonBases chainId={chainId} onSelect={handleTokenSelect} selectedTokenAddress={hiddenToken} />
|
||||
<CommonBases chainId={chainId} onSelect={handleCurrencySelect} selectedCurrency={hiddenCurrency} />
|
||||
)}
|
||||
<RowBetween>
|
||||
<Text fontSize={14} fontWeight={500}>
|
||||
@@ -160,13 +182,12 @@ export default function TokenSearchModal({
|
||||
</RowBetween>
|
||||
</PaddedColumn>
|
||||
<div style={{ width: '100%', height: '1px', backgroundColor: theme.bg2 }} />
|
||||
<TokenList
|
||||
tokens={filteredSortedTokens}
|
||||
allTokenBalances={allTokenBalances}
|
||||
onTokenSelect={handleTokenSelect}
|
||||
otherSelectedText={otherSelectedText}
|
||||
otherToken={otherSelectedTokenAddress}
|
||||
selectedToken={hiddenToken}
|
||||
<CurrencyList
|
||||
currencies={filteredSortedTokens}
|
||||
allBalances={allTokenBalances}
|
||||
onCurrencySelect={handleCurrencySelect}
|
||||
otherCurrency={otherSelectedCurrency}
|
||||
selectedCurrency={hiddenCurrency}
|
||||
showSendWithSwap={showSendWithSwap}
|
||||
/>
|
||||
<div style={{ width: '100%', height: '1px', backgroundColor: theme.bg2 }} />
|
||||
@@ -1,58 +0,0 @@
|
||||
import { JSBI, Pair, TokenAmount } from '@uniswap/sdk'
|
||||
import React from 'react'
|
||||
import { FixedSizeList } from 'react-window'
|
||||
import { Text } from 'rebass'
|
||||
import { ButtonPrimary } from '../Button'
|
||||
import DoubleTokenLogo from '../DoubleLogo'
|
||||
import { RowFixed } from '../Row'
|
||||
import { MenuItem, ModalInfo } from './styleds'
|
||||
|
||||
export default function PairList({
|
||||
pairs,
|
||||
focusTokenAddress,
|
||||
pairBalances,
|
||||
onSelectPair,
|
||||
onAddLiquidity = onSelectPair
|
||||
}: {
|
||||
pairs: Pair[]
|
||||
focusTokenAddress?: string
|
||||
pairBalances: { [pairAddress: string]: TokenAmount }
|
||||
onSelectPair: (pair: Pair) => void
|
||||
onAddLiquidity: (pair: Pair) => void
|
||||
}) {
|
||||
if (pairs.length === 0) {
|
||||
return <ModalInfo>No Pools Found</ModalInfo>
|
||||
}
|
||||
|
||||
return (
|
||||
<FixedSizeList itemSize={56} height={500} itemCount={pairs.length} width="100%" style={{ flex: '1' }}>
|
||||
{({ index, style }) => {
|
||||
const pair = pairs[index]
|
||||
|
||||
// the focused token is shown first
|
||||
const tokenA = focusTokenAddress === pair.token1.address ? pair.token1 : pair.token0
|
||||
const tokenB = tokenA === pair.token0 ? pair.token1 : pair.token0
|
||||
|
||||
const pairAddress = pair.liquidityToken.address
|
||||
const balance = pairBalances[pairAddress]?.toSignificant(6)
|
||||
const zeroBalance = pairBalances[pairAddress]?.raw && JSBI.equal(pairBalances[pairAddress].raw, JSBI.BigInt(0))
|
||||
|
||||
const selectPair = () => onSelectPair(pair)
|
||||
const addLiquidity = () => onAddLiquidity(pair)
|
||||
|
||||
return (
|
||||
<MenuItem style={style} onClick={selectPair}>
|
||||
<RowFixed>
|
||||
<DoubleTokenLogo a0={tokenA.address} a1={tokenB.address} size={24} margin={true} />
|
||||
<Text fontWeight={500} fontSize={16}>{`${tokenA.symbol}/${tokenB.symbol}`}</Text>
|
||||
</RowFixed>
|
||||
|
||||
<ButtonPrimary padding={'6px 8px'} width={'fit-content'} borderRadius={'12px'} onClick={addLiquidity}>
|
||||
{balance ? (zeroBalance ? 'Join' : 'Add Liquidity') : 'Join'}
|
||||
</ButtonPrimary>
|
||||
</MenuItem>
|
||||
)
|
||||
}}
|
||||
</FixedSizeList>
|
||||
)
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
import { Pair } from '@uniswap/sdk'
|
||||
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { isMobile } from 'react-device-detect'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RouteComponentProps, withRouter } from 'react-router-dom'
|
||||
import { Text } from 'rebass'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import Card from '../../components/Card'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useAllTokens } from '../../hooks/Tokens'
|
||||
import { useAllDummyPairs } from '../../state/user/hooks'
|
||||
import { useTokenBalances } from '../../state/wallet/hooks'
|
||||
import { CloseIcon, StyledInternalLink } from '../../theme/components'
|
||||
import { isAddress } from '../../utils'
|
||||
import Column from '../Column'
|
||||
import Modal from '../Modal'
|
||||
import QuestionHelper from '../QuestionHelper'
|
||||
import { AutoRow, RowBetween } from '../Row'
|
||||
import { filterPairs } from './filtering'
|
||||
import PairList from './PairList'
|
||||
import { pairComparator } from './sorting'
|
||||
import { PaddedColumn, SearchInput } from './styleds'
|
||||
|
||||
interface PairSearchModalProps extends RouteComponentProps {
|
||||
isOpen?: boolean
|
||||
onDismiss?: () => void
|
||||
}
|
||||
|
||||
function PairSearchModal({ history, isOpen, onDismiss }: PairSearchModalProps) {
|
||||
const { t } = useTranslation()
|
||||
const { account } = useActiveWeb3React()
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState<string>('')
|
||||
|
||||
const allTokens = useAllTokens()
|
||||
const allPairs = useAllDummyPairs()
|
||||
|
||||
const allPairBalances = useTokenBalances(
|
||||
account,
|
||||
allPairs.map(p => p.liquidityToken)
|
||||
)
|
||||
|
||||
// clear the input on open
|
||||
useEffect(() => {
|
||||
if (isOpen) setSearchQuery('')
|
||||
}, [isOpen, setSearchQuery])
|
||||
|
||||
// manage focus on modal show
|
||||
const inputRef = useRef<HTMLInputElement>()
|
||||
function onInput(event) {
|
||||
const input = event.target.value
|
||||
const checksummedInput = isAddress(input)
|
||||
setSearchQuery(checksummedInput || input)
|
||||
}
|
||||
|
||||
const filteredPairs = useMemo(() => {
|
||||
return filterPairs(allPairs, searchQuery)
|
||||
}, [allPairs, searchQuery])
|
||||
|
||||
const sortedPairList = useMemo(() => {
|
||||
const query = searchQuery.toLowerCase()
|
||||
const queryMatches = (pair: Pair): boolean =>
|
||||
pair.token0.symbol.toLowerCase() === query || pair.token1.symbol.toLowerCase() === query
|
||||
return filteredPairs.sort((a, b): number => {
|
||||
const [aMatches, bMatches] = [queryMatches(a), queryMatches(b)]
|
||||
if (aMatches && !bMatches) return -1
|
||||
if (bMatches && !aMatches) return 1
|
||||
const balanceA = allPairBalances[a.liquidityToken.address]
|
||||
const balanceB = allPairBalances[b.liquidityToken.address]
|
||||
return pairComparator(a, b, balanceA, balanceB)
|
||||
})
|
||||
}, [searchQuery, filteredPairs, allPairBalances])
|
||||
|
||||
const selectPair = useCallback(
|
||||
(pair: Pair) => {
|
||||
history.push(`/add/${pair.token0.address}-${pair.token1.address}`)
|
||||
},
|
||||
[history]
|
||||
)
|
||||
|
||||
const focusedToken = Object.values(allTokens ?? {}).filter(token => {
|
||||
return token.symbol.toLowerCase() === searchQuery || searchQuery === token.address
|
||||
})[0]
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onDismiss={onDismiss}
|
||||
maxHeight={70}
|
||||
initialFocusRef={isMobile ? undefined : inputRef}
|
||||
minHeight={70}
|
||||
>
|
||||
<Column style={{ width: '100%' }}>
|
||||
<PaddedColumn gap="20px">
|
||||
<RowBetween>
|
||||
<Text fontWeight={500} fontSize={16}>
|
||||
Select a pool
|
||||
<QuestionHelper text="Find a pair by searching for its name below." />
|
||||
</Text>
|
||||
<CloseIcon onClick={onDismiss} />
|
||||
</RowBetween>
|
||||
<SearchInput
|
||||
type="text"
|
||||
id="token-search-input"
|
||||
placeholder={t('tokenSearchPlaceholder')}
|
||||
value={searchQuery}
|
||||
ref={inputRef}
|
||||
onChange={onInput}
|
||||
/>
|
||||
<RowBetween>
|
||||
<Text fontSize={14} fontWeight={500}>
|
||||
Pool Name
|
||||
</Text>
|
||||
</RowBetween>
|
||||
</PaddedColumn>
|
||||
<div style={{ width: '100%', height: '1px', backgroundColor: theme.bg2 }} />
|
||||
<PairList
|
||||
pairs={sortedPairList}
|
||||
focusTokenAddress={focusedToken?.address}
|
||||
onAddLiquidity={selectPair}
|
||||
onSelectPair={selectPair}
|
||||
pairBalances={allPairBalances}
|
||||
/>
|
||||
<div style={{ width: '100%', height: '1px', backgroundColor: theme.bg2 }} />
|
||||
<Card>
|
||||
<AutoRow justify={'center'}>
|
||||
<div>
|
||||
<Text fontWeight={500}>
|
||||
{!isMobile && "Don't see a pool? "}
|
||||
<StyledInternalLink to="/find">{!isMobile ? 'Import it.' : 'Import pool.'}</StyledInternalLink>
|
||||
</Text>
|
||||
</div>
|
||||
</AutoRow>
|
||||
</Card>
|
||||
</Column>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default withRouter(PairSearchModal)
|
||||
@@ -1,138 +0,0 @@
|
||||
import { JSBI, Token, TokenAmount } from '@uniswap/sdk'
|
||||
import React, { useContext } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { FixedSizeList } from 'react-window'
|
||||
import { Text } from 'rebass'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useAllTokens } from '../../hooks/Tokens'
|
||||
import { useAddUserToken, useRemoveUserAddedToken } from '../../state/user/hooks'
|
||||
import { LinkStyledButton, TYPE } from '../../theme'
|
||||
import { ButtonSecondary } from '../Button'
|
||||
import Column, { AutoColumn } from '../Column'
|
||||
import { RowFixed } from '../Row'
|
||||
import TokenLogo from '../TokenLogo'
|
||||
import { FadedSpan, GreySpan, MenuItem, ModalInfo } from './styleds'
|
||||
import Loader from '../Loader'
|
||||
import { isDefaultToken, isCustomAddedToken } from '../../utils'
|
||||
|
||||
export default function TokenList({
|
||||
tokens,
|
||||
allTokenBalances,
|
||||
selectedToken,
|
||||
onTokenSelect,
|
||||
otherToken,
|
||||
showSendWithSwap,
|
||||
otherSelectedText
|
||||
}: {
|
||||
tokens: Token[]
|
||||
selectedToken: string
|
||||
allTokenBalances: { [tokenAddress: string]: TokenAmount }
|
||||
onTokenSelect: (tokenAddress: string) => void
|
||||
otherToken: string
|
||||
showSendWithSwap?: boolean
|
||||
otherSelectedText: string
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
const theme = useContext(ThemeContext)
|
||||
const allTokens = useAllTokens()
|
||||
const addToken = useAddUserToken()
|
||||
const removeToken = useRemoveUserAddedToken()
|
||||
|
||||
if (tokens.length === 0) {
|
||||
return <ModalInfo>{t('noToken')}</ModalInfo>
|
||||
}
|
||||
|
||||
return (
|
||||
<FixedSizeList
|
||||
width="100%"
|
||||
height={500}
|
||||
itemCount={tokens.length}
|
||||
itemSize={56}
|
||||
style={{ flex: '1' }}
|
||||
itemKey={index => tokens[index].address}
|
||||
>
|
||||
{({ index, style }) => {
|
||||
const token = tokens[index]
|
||||
const { address, symbol } = token
|
||||
|
||||
const isDefault = isDefaultToken(token)
|
||||
const customAdded = isCustomAddedToken(allTokens, token)
|
||||
const balance = allTokenBalances[address]
|
||||
|
||||
const zeroBalance = balance && JSBI.equal(JSBI.BigInt(0), balance.raw)
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
style={style}
|
||||
key={address}
|
||||
className={`token-item-${address}`}
|
||||
onClick={() => (selectedToken && selectedToken === address ? null : onTokenSelect(address))}
|
||||
disabled={selectedToken && selectedToken === address}
|
||||
selected={otherToken === address}
|
||||
>
|
||||
<RowFixed>
|
||||
<TokenLogo address={address} size={'24px'} style={{ marginRight: '14px' }} />
|
||||
<Column>
|
||||
<Text fontWeight={500}>
|
||||
{symbol}
|
||||
{otherToken === address && <GreySpan> ({otherSelectedText})</GreySpan>}
|
||||
</Text>
|
||||
<FadedSpan>
|
||||
{customAdded ? (
|
||||
<TYPE.main fontWeight={500}>
|
||||
Added by user
|
||||
<LinkStyledButton
|
||||
onClick={event => {
|
||||
event.stopPropagation()
|
||||
removeToken(chainId, address)
|
||||
}}
|
||||
>
|
||||
(Remove)
|
||||
</LinkStyledButton>
|
||||
</TYPE.main>
|
||||
) : null}
|
||||
{!isDefault && !customAdded ? (
|
||||
<TYPE.main fontWeight={500}>
|
||||
Found by address
|
||||
<LinkStyledButton
|
||||
onClick={event => {
|
||||
event.stopPropagation()
|
||||
addToken(token)
|
||||
}}
|
||||
>
|
||||
(Add)
|
||||
</LinkStyledButton>
|
||||
</TYPE.main>
|
||||
) : null}
|
||||
</FadedSpan>
|
||||
</Column>
|
||||
</RowFixed>
|
||||
<AutoColumn>
|
||||
{balance ? (
|
||||
<Text>
|
||||
{zeroBalance && showSendWithSwap ? (
|
||||
<ButtonSecondary padding={'4px 8px'}>
|
||||
<Text textAlign="center" fontWeight={500} fontSize={14} color={theme.primary1}>
|
||||
Send With Swap
|
||||
</Text>
|
||||
</ButtonSecondary>
|
||||
) : balance ? (
|
||||
balance.toSignificant(6)
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</Text>
|
||||
) : account ? (
|
||||
<Loader />
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</AutoColumn>
|
||||
</MenuItem>
|
||||
)
|
||||
}}
|
||||
</FixedSizeList>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { isAddress } from '../../utils'
|
||||
import { Pair, Token } from '@uniswap/sdk'
|
||||
import { Token } from '@uniswap/sdk'
|
||||
|
||||
export function filterTokens(tokens: Token[], search: string): Token[] {
|
||||
if (search.length === 0) return tokens
|
||||
@@ -34,27 +34,3 @@ export function filterTokens(tokens: Token[], search: string): Token[] {
|
||||
return matchesSearch(symbol) || matchesSearch(name)
|
||||
})
|
||||
}
|
||||
|
||||
export function filterPairs(pairs: Pair[], search: string): Pair[] {
|
||||
if (search.trim().length === 0) return pairs
|
||||
|
||||
const addressSearch = isAddress(search)
|
||||
if (addressSearch) {
|
||||
return pairs.filter(p => {
|
||||
return (
|
||||
p.token0.address === addressSearch ||
|
||||
p.token1.address === addressSearch ||
|
||||
p.liquidityToken.address === addressSearch
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const lowerSearch = search.toLowerCase()
|
||||
return pairs.filter(pair => {
|
||||
const pairExpressionA = `${pair.token0.symbol}/${pair.token1.symbol}`.toLowerCase()
|
||||
if (pairExpressionA.startsWith(lowerSearch)) return true
|
||||
const pairExpressionB = `${pair.token1.symbol}/${pair.token0.symbol}`.toLowerCase()
|
||||
if (pairExpressionB.startsWith(lowerSearch)) return true
|
||||
return filterTokens([pair.token0, pair.token1], search).length > 0
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Token, TokenAmount, WETH, Pair } from '@uniswap/sdk'
|
||||
import { Token, TokenAmount, WETH } from '@uniswap/sdk'
|
||||
import { useMemo } from 'react'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useAllTokenBalancesTreatingWETHasETH } from '../../state/wallet/hooks'
|
||||
import { DUMMY_PAIRS_TO_PIN } from '../../constants'
|
||||
import { useAllTokenBalances } from '../../state/wallet/hooks'
|
||||
|
||||
// compare two token amounts with highest one coming first
|
||||
function balanceComparator(balanceA?: TokenAmount, balanceB?: TokenAmount) {
|
||||
@@ -16,26 +15,6 @@ function balanceComparator(balanceA?: TokenAmount, balanceB?: TokenAmount) {
|
||||
return 0
|
||||
}
|
||||
|
||||
// compare two pairs, favoring "pinned" pairs, and falling back to balances
|
||||
export function pairComparator(pairA: Pair, pairB: Pair, balanceA?: TokenAmount, balanceB?: TokenAmount) {
|
||||
const aShouldBePinned =
|
||||
DUMMY_PAIRS_TO_PIN[pairA?.token0?.chainId]?.some(
|
||||
dummyPairToPin => dummyPairToPin.liquidityToken.address === pairA?.liquidityToken?.address
|
||||
) ?? false
|
||||
const bShouldBePinned =
|
||||
DUMMY_PAIRS_TO_PIN[pairB?.token0?.chainId]?.some(
|
||||
dummyPairToPin => dummyPairToPin.liquidityToken.address === pairB?.liquidityToken?.address
|
||||
) ?? false
|
||||
|
||||
if (aShouldBePinned && !bShouldBePinned) {
|
||||
return -1
|
||||
} else if (!aShouldBePinned && bShouldBePinned) {
|
||||
return 1
|
||||
} else {
|
||||
return balanceComparator(balanceA, balanceB)
|
||||
}
|
||||
}
|
||||
|
||||
function getTokenComparator(
|
||||
weth: Token | undefined,
|
||||
balances: { [tokenAddress: string]: TokenAmount }
|
||||
@@ -65,7 +44,7 @@ function getTokenComparator(
|
||||
export function useTokenComparator(inverted: boolean): (tokenA: Token, tokenB: Token) => number {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const weth = WETH[chainId]
|
||||
const balances = useAllTokenBalancesTreatingWETHasETH()
|
||||
const balances = useAllTokenBalances()
|
||||
const comparator = useMemo(() => getTokenComparator(weth, balances ?? {}), [balances, weth])
|
||||
return useMemo(() => {
|
||||
if (inverted) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import styled from 'styled-components'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { AutoRow, RowBetween, RowFixed } from '../Row'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
|
||||
export const ModalInfo = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
@@ -61,21 +61,6 @@ export const MenuItem = styled(RowBetween)`
|
||||
opacity: ${({ disabled, selected }) => (disabled || selected ? 0.5 : 1)};
|
||||
`
|
||||
|
||||
export const BaseWrapper = styled(AutoRow)<{ disable?: boolean }>`
|
||||
border: 1px solid ${({ theme, disable }) => (disable ? 'transparent' : theme.bg3)};
|
||||
padding: 0 6px;
|
||||
border-radius: 10px;
|
||||
width: 120px;
|
||||
|
||||
:hover {
|
||||
cursor: ${({ disable }) => !disable && 'pointer'};
|
||||
background-color: ${({ theme, disable }) => !disable && theme.bg2};
|
||||
}
|
||||
|
||||
background-color: ${({ theme, disable }) => disable && theme.bg3};
|
||||
opacity: ${({ disable }) => disable && '0.4'};
|
||||
`
|
||||
|
||||
export const SearchInput = styled(Input)`
|
||||
transition: border 100ms;
|
||||
:focus {
|
||||
|
||||
@@ -88,6 +88,9 @@ const MenuFlyout = styled.span`
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
|
||||
0px 24px 32px rgba(0, 0, 0, 0.01);
|
||||
|
||||
border: 1px solid ${({ theme }) => theme.bg3};
|
||||
|
||||
border-radius: 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -156,7 +159,7 @@ export default function SettingsTab() {
|
||||
|
||||
return (
|
||||
<StyledMenu ref={node}>
|
||||
<Modal isOpen={showConfirmation} onDismiss={() => setShowConfirmation(false)}>
|
||||
<Modal isOpen={showConfirmation} onDismiss={() => setShowConfirmation(false)} maxHeight={100}>
|
||||
<ModalContentWrapper>
|
||||
<AutoColumn gap="lg">
|
||||
<RowBetween style={{ padding: '0 2rem' }}>
|
||||
@@ -233,7 +236,10 @@ export default function SettingsTab() {
|
||||
toggleExpertMode()
|
||||
setShowConfirmation(false)
|
||||
}
|
||||
: () => setShowConfirmation(true)
|
||||
: () => {
|
||||
toggle()
|
||||
setShowConfirmation(true)
|
||||
}
|
||||
}
|
||||
/>
|
||||
</RowBetween>
|
||||
|
||||
@@ -21,15 +21,15 @@ enum DeadlineError {
|
||||
const FancyButton = styled.button`
|
||||
color: ${({ theme }) => theme.text1};
|
||||
align-items: center;
|
||||
min-width: 55px;
|
||||
height: 2rem;
|
||||
border-radius: 36px;
|
||||
font-size: 12px;
|
||||
width: auto;
|
||||
min-width: 3rem;
|
||||
border: 1px solid ${({ theme }) => theme.bg3};
|
||||
outline: none;
|
||||
background: ${({ theme }) => theme.bg1};
|
||||
:hover {
|
||||
cursor: inherit;
|
||||
border: 1px solid ${({ theme }) => theme.bg4};
|
||||
}
|
||||
:focus {
|
||||
@@ -48,9 +48,8 @@ const Option = styled(FancyButton)<{ active: boolean }>`
|
||||
|
||||
const Input = styled.input`
|
||||
background: ${({ theme }) => theme.bg1};
|
||||
flex-grow: 1;
|
||||
font-size: 12px;
|
||||
min-width: 20px;
|
||||
font-size: 16px;
|
||||
width: auto;
|
||||
outline: none;
|
||||
&::-webkit-outer-spin-button,
|
||||
&::-webkit-inner-spin-button {
|
||||
@@ -64,6 +63,7 @@ const OptionCustom = styled(FancyButton)<{ active?: boolean; warning?: boolean }
|
||||
height: 2rem;
|
||||
position: relative;
|
||||
padding: 0 0.75rem;
|
||||
flex: 1;
|
||||
border: ${({ theme, active, warning }) => active && `1px solid ${warning ? theme.red1 : theme.primary1}`};
|
||||
:hover {
|
||||
border: ${({ theme, active, warning }) =>
|
||||
@@ -78,6 +78,13 @@ const OptionCustom = styled(FancyButton)<{ active?: boolean; warning?: boolean }
|
||||
}
|
||||
`
|
||||
|
||||
const SlippageEmojiContainer = styled.span`
|
||||
color: #f3841e;
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
display: none;
|
||||
`}
|
||||
`
|
||||
|
||||
export interface SlippageTabsProps {
|
||||
rawSlippage: number
|
||||
setRawSlippage: (rawSlippage: number) => void
|
||||
@@ -182,9 +189,11 @@ export default function SlippageTabs({ rawSlippage, setRawSlippage, deadline, se
|
||||
<RowBetween>
|
||||
{!!slippageInput &&
|
||||
(slippageError === SlippageError.RiskyLow || slippageError === SlippageError.RiskyHigh) ? (
|
||||
<span role="img" aria-label="warning" style={{ color: '#F3841E' }}>
|
||||
⚠️
|
||||
</span>
|
||||
<SlippageEmojiContainer>
|
||||
<span role="img" aria-label="warning">
|
||||
⚠️
|
||||
</span>
|
||||
</SlippageEmojiContainer>
|
||||
) : null}
|
||||
<Input
|
||||
ref={inputRef}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
import React, { useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { isAddress } from '../../utils'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { WETH } from '@uniswap/sdk'
|
||||
|
||||
import EthereumLogo from '../../assets/images/ethereum-logo.png'
|
||||
|
||||
const getTokenLogoURL = address =>
|
||||
`https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/${address}/logo.png`
|
||||
const NO_LOGO_ADDRESSES: { [tokenAddress: string]: true } = {}
|
||||
|
||||
const Image = styled.img<{ size: string }>`
|
||||
width: ${({ size }) => size};
|
||||
height: ${({ size }) => size};
|
||||
background-color: white;
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.075);
|
||||
`
|
||||
|
||||
const Emoji = styled.span<{ size?: string }>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: ${({ size }) => size};
|
||||
width: ${({ size }) => size};
|
||||
height: ${({ size }) => size};
|
||||
margin-bottom: -4px;
|
||||
`
|
||||
|
||||
const StyledEthereumLogo = styled.img<{ size: string }>`
|
||||
width: ${({ size }) => size};
|
||||
height: ${({ size }) => size};
|
||||
box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.075);
|
||||
border-radius: 24px;
|
||||
`
|
||||
|
||||
export default function TokenLogo({
|
||||
address,
|
||||
size = '24px',
|
||||
...rest
|
||||
}: {
|
||||
address?: string
|
||||
size?: string
|
||||
style?: React.CSSProperties
|
||||
}) {
|
||||
const [, refresh] = useState<number>(0)
|
||||
const { chainId } = useActiveWeb3React()
|
||||
|
||||
let path = ''
|
||||
const validated = isAddress(address)
|
||||
// hard code to show ETH instead of WETH in UI
|
||||
if (validated === WETH[chainId].address) {
|
||||
return <StyledEthereumLogo src={EthereumLogo} size={size} {...rest} />
|
||||
} else if (!NO_LOGO_ADDRESSES[address] && validated) {
|
||||
path = getTokenLogoURL(validated)
|
||||
} else {
|
||||
return (
|
||||
<Emoji {...rest} size={size}>
|
||||
<span role="img" aria-label="Thinking">
|
||||
🤔
|
||||
</span>
|
||||
</Emoji>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Image
|
||||
{...rest}
|
||||
// alt={address}
|
||||
src={path}
|
||||
size={size}
|
||||
onError={() => {
|
||||
NO_LOGO_ADDRESSES[address] = true
|
||||
refresh(i => i + 1)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,17 +1,18 @@
|
||||
import { Token } from '@uniswap/sdk'
|
||||
import { Currency, Token } from '@uniswap/sdk'
|
||||
import { transparentize } from 'polished'
|
||||
import React, { useMemo } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { ReactComponent as Close } from '../../assets/images/x.svg'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useAllTokens } from '../../hooks/Tokens'
|
||||
import { useDefaultTokenList } from '../../state/lists/hooks'
|
||||
import { Field } from '../../state/swap/actions'
|
||||
import { useTokenWarningDismissal } from '../../state/user/hooks'
|
||||
import { ExternalLink, TYPE } from '../../theme'
|
||||
import { getEtherscanLink, isDefaultToken } from '../../utils'
|
||||
import PropsOfExcluding from '../../utils/props-of-excluding'
|
||||
import QuestionHelper from '../QuestionHelper'
|
||||
import TokenLogo from '../TokenLogo'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
|
||||
const Wrapper = styled.div<{ error: boolean }>`
|
||||
background: ${({ theme, error }) => transparentize(0.9, error ? theme.red1 : theme.yellow1)};
|
||||
@@ -67,8 +68,8 @@ interface TokenWarningCardProps extends PropsOfExcluding<typeof Wrapper, 'error'
|
||||
|
||||
export default function TokenWarningCard({ token, ...rest }: TokenWarningCardProps) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
|
||||
const isDefault = isDefaultToken(token)
|
||||
const defaultTokens = useDefaultTokenList()
|
||||
const isDefault = isDefaultToken(defaultTokens, token)
|
||||
|
||||
const tokenSymbol = token?.symbol?.toLowerCase() ?? ''
|
||||
const tokenName = token?.name?.toLowerCase() ?? ''
|
||||
@@ -103,13 +104,13 @@ export default function TokenWarningCard({ token, ...rest }: TokenWarningCardPro
|
||||
<QuestionHelper text={duplicateNameOrSymbol ? DUPLICATE_NAME_HELP_TEXT : HELP_TEXT} />
|
||||
</Row>
|
||||
<Row>
|
||||
<TokenLogo address={token.address} />
|
||||
<CurrencyLogo currency={token} />
|
||||
<div style={{ fontWeight: 500 }}>
|
||||
{token && token.name && token.symbol && token.name !== token.symbol
|
||||
? `${token.name} (${token.symbol})`
|
||||
: token.name || token.symbol}
|
||||
</div>
|
||||
<ExternalLink style={{ fontWeight: 400 }} href={getEtherscanLink(chainId, token.address, 'address')}>
|
||||
<ExternalLink style={{ fontWeight: 400 }} href={getEtherscanLink(chainId, token.address, 'token')}>
|
||||
(View on Etherscan)
|
||||
</ExternalLink>
|
||||
</Row>
|
||||
@@ -127,11 +128,13 @@ const WarningContainer = styled.div`
|
||||
padding-right: 1rem;
|
||||
`
|
||||
|
||||
export function TokenWarningCards({ tokens }: { tokens: { [field in Field]?: Token } }) {
|
||||
export function TokenWarningCards({ currencies }: { currencies: { [field in Field]?: Currency } }) {
|
||||
return (
|
||||
<WarningContainer>
|
||||
{Object.keys(tokens).map(field =>
|
||||
tokens[field] ? <TokenWarningCard style={{ marginBottom: 14 }} key={field} token={tokens[field]} /> : null
|
||||
{Object.keys(currencies).map(field =>
|
||||
currencies[field] instanceof Token ? (
|
||||
<TokenWarningCard style={{ marginBottom: 14 }} key={field} token={currencies[field]} />
|
||||
) : null
|
||||
)}
|
||||
</WarningContainer>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import Popover, { PopoverProps } from '../Popover'
|
||||
|
||||
@@ -16,3 +16,16 @@ interface TooltipProps extends Omit<PopoverProps, 'content'> {
|
||||
export default function Tooltip({ text, ...rest }: TooltipProps) {
|
||||
return <Popover content={<TooltipContainer>{text}</TooltipContainer>} {...rest} />
|
||||
}
|
||||
|
||||
export function MouseoverTooltip({ children, ...rest }: Omit<TooltipProps, 'show'>) {
|
||||
const [show, setShow] = useState(false)
|
||||
const open = useCallback(() => setShow(true), [setShow])
|
||||
const close = useCallback(() => setShow(false), [setShow])
|
||||
return (
|
||||
<Tooltip {...rest} show={show}>
|
||||
<div onMouseEnter={open} onMouseLeave={close}>
|
||||
{children}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
import React, { useCallback, useContext, useState } from 'react'
|
||||
import { AlertCircle, CheckCircle } from 'react-feather'
|
||||
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import useInterval from '../../hooks/useInterval'
|
||||
import { useRemovePopup } from '../../state/application/hooks'
|
||||
import { TYPE } from '../../theme'
|
||||
|
||||
import { ExternalLink } from '../../theme/components'
|
||||
import { getEtherscanLink } from '../../utils'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { AutoRow } from '../Row'
|
||||
|
||||
const Fader = styled.div<{ count: number }>`
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
width: ${({ count }) => `calc(100% - (100% / ${150 / count}))`};
|
||||
height: 2px;
|
||||
background-color: ${({ theme }) => theme.bg3};
|
||||
transition: width 100ms linear;
|
||||
`
|
||||
|
||||
const delay = 100
|
||||
|
||||
export default function TxnPopup({
|
||||
hash,
|
||||
success,
|
||||
summary,
|
||||
popKey
|
||||
}: {
|
||||
hash: string
|
||||
success?: boolean
|
||||
summary?: string
|
||||
popKey?: string
|
||||
}) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const [count, setCount] = useState(1)
|
||||
|
||||
const [isRunning, setIsRunning] = useState(true)
|
||||
const removePopup = useRemovePopup()
|
||||
|
||||
const removeThisPopup = useCallback(() => removePopup(popKey), [popKey, removePopup])
|
||||
|
||||
useInterval(
|
||||
() => {
|
||||
count > 150 ? removeThisPopup() : setCount(count + 1)
|
||||
},
|
||||
isRunning ? delay : null
|
||||
)
|
||||
|
||||
const handleMouseEnter = useCallback(() => setIsRunning(false), [])
|
||||
const handleMouseLeave = useCallback(() => setIsRunning(true), [])
|
||||
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
return (
|
||||
<AutoRow onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
|
||||
<div style={{ paddingRight: 16 }}>
|
||||
{success ? <CheckCircle color={theme.green1} size={24} /> : <AlertCircle color={theme.red1} size={24} />}
|
||||
</div>
|
||||
<AutoColumn gap="8px">
|
||||
<TYPE.body fontWeight={500}>
|
||||
{summary ? summary : 'Hash: ' + hash.slice(0, 8) + '...' + hash.slice(58, 65)}
|
||||
</TYPE.body>
|
||||
<ExternalLink href={getEtherscanLink(chainId, hash, 'transaction')}>View on Etherscan</ExternalLink>
|
||||
</AutoColumn>
|
||||
<Fader count={count} />
|
||||
</AutoRow>
|
||||
)
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { useWeb3React, UnsupportedChainIdError } from '@web3-react/core'
|
||||
import { darken, lighten } from 'polished'
|
||||
import { Activity } from 'react-feather'
|
||||
import useENSName from '../../hooks/useENSName'
|
||||
import { useHasSocks } from '../../hooks/useSocksBalance'
|
||||
import { useWalletModalToggle } from '../../state/application/hooks'
|
||||
import { TransactionDetails } from '../../state/transactions/reducer'
|
||||
|
||||
@@ -125,12 +126,18 @@ function recentTransactionsOnly(a: TransactionDetails) {
|
||||
return new Date().getTime() - a.addedTime < 86_400_000
|
||||
}
|
||||
|
||||
const SOCK = (
|
||||
<span role="img" aria-label="has socks emoji" style={{ marginTop: -4, marginBottom: -4 }}>
|
||||
🧦
|
||||
</span>
|
||||
)
|
||||
|
||||
export default function Web3Status() {
|
||||
const { t } = useTranslation()
|
||||
const { active, account, connector, error } = useWeb3React()
|
||||
const contextNetwork = useWeb3React(NetworkContextName)
|
||||
|
||||
const ENSName = useENSName(account)
|
||||
const { ENSName } = useENSName(account)
|
||||
|
||||
const allTransactions = useAllTransactions()
|
||||
|
||||
@@ -143,7 +150,7 @@ export default function Web3Status() {
|
||||
const confirmed = sortedRecentTransactions.filter(tx => tx.receipt).map(tx => tx.hash)
|
||||
|
||||
const hasPendingTransactions = !!pending.length
|
||||
|
||||
const hasSocks = useHasSocks()
|
||||
const toggleWalletModal = useWalletModalToggle()
|
||||
|
||||
// handle the logo we want to show with the account
|
||||
@@ -186,7 +193,10 @@ export default function Web3Status() {
|
||||
<Text>{pending?.length} Pending</Text> <Loader stroke="white" />
|
||||
</RowBetween>
|
||||
) : (
|
||||
<Text>{ENSName || shortenAddress(account)}</Text>
|
||||
<>
|
||||
{hasSocks ? SOCK : null}
|
||||
<Text>{ENSName || shortenAddress(account)}</Text>
|
||||
</>
|
||||
)}
|
||||
{!hasPendingTransactions && getStatusIcon()}
|
||||
</Web3StatusConnected>
|
||||
|
||||
@@ -31,8 +31,10 @@ function TradeSummary({ trade, allowedSlippage }: { trade: Trade; allowedSlippag
|
||||
<RowFixed>
|
||||
<TYPE.black color={theme.text1} fontSize={14}>
|
||||
{isExactIn
|
||||
? `${slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(4)} ${trade.outputAmount.token.symbol}` ?? '-'
|
||||
: `${slippageAdjustedAmounts[Field.INPUT]?.toSignificant(4)} ${trade.inputAmount.token.symbol}` ?? '-'}
|
||||
? `${slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(4)} ${trade.outputAmount.currency.symbol}` ??
|
||||
'-'
|
||||
: `${slippageAdjustedAmounts[Field.INPUT]?.toSignificant(4)} ${trade.inputAmount.currency.symbol}` ??
|
||||
'-'}
|
||||
</TYPE.black>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
@@ -54,7 +56,7 @@ function TradeSummary({ trade, allowedSlippage }: { trade: Trade; allowedSlippag
|
||||
<QuestionHelper text="A portion of each trade (0.30%) goes to liquidity providers as a protocol incentive." />
|
||||
</RowFixed>
|
||||
<TYPE.black fontSize={14} color={theme.text1}>
|
||||
{realizedLPFee ? `${realizedLPFee.toSignificant(4)} ${trade.inputAmount.token.symbol}` : '-'}
|
||||
{realizedLPFee ? `${realizedLPFee.toSignificant(4)} ${trade.inputAmount.currency.symbol}` : '-'}
|
||||
</TYPE.black>
|
||||
</RowBetween>
|
||||
</AutoColumn>
|
||||
|
||||
@@ -24,7 +24,7 @@ export default function AdvancedSwapDetailsDropdown({ trade, ...rest }: Advanced
|
||||
|
||||
return (
|
||||
<AdvancedDetailsFooter show={Boolean(trade)}>
|
||||
<AdvancedSwapDetails {...rest} trade={lastTrade} />
|
||||
<AdvancedSwapDetails {...rest} trade={trade ?? lastTrade} />
|
||||
</AdvancedDetailsFooter>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Percent, TokenAmount, Trade, TradeType } from '@uniswap/sdk'
|
||||
import { CurrencyAmount, Percent, Trade, TradeType } from '@uniswap/sdk'
|
||||
import React, { useContext } from 'react'
|
||||
import { Repeat } from 'react-feather'
|
||||
import { Text } from 'rebass'
|
||||
@@ -29,10 +29,10 @@ export default function SwapModalFooter({
|
||||
showInverted: boolean
|
||||
setShowInverted: (inverted: boolean) => void
|
||||
severity: number
|
||||
slippageAdjustedAmounts?: { [field in Field]?: TokenAmount }
|
||||
slippageAdjustedAmounts?: { [field in Field]?: CurrencyAmount }
|
||||
onSwap: () => any
|
||||
parsedAmounts?: { [field in Field]?: TokenAmount }
|
||||
realizedLPFee?: TokenAmount
|
||||
parsedAmounts?: { [field in Field]?: CurrencyAmount }
|
||||
realizedLPFee?: CurrencyAmount
|
||||
priceImpactWithoutFee?: Percent
|
||||
confirmText: string
|
||||
}) {
|
||||
@@ -71,7 +71,7 @@ export default function SwapModalFooter({
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<TYPE.black fontSize={14} fontWeight={400} color={theme.text2}>
|
||||
{trade?.tradeType === TradeType.EXACT_INPUT ? 'Minimum sent' : 'Maximum sold'}
|
||||
{trade?.tradeType === TradeType.EXACT_INPUT ? 'Minimum received' : 'Maximum sold'}
|
||||
</TYPE.black>
|
||||
<QuestionHelper text="Your transaction will revert if there is a large, unfavorable price movement before it is confirmed." />
|
||||
</RowFixed>
|
||||
@@ -84,8 +84,8 @@ export default function SwapModalFooter({
|
||||
{parsedAmounts[Field.OUTPUT] && parsedAmounts[Field.INPUT] && (
|
||||
<TYPE.black fontSize={14} marginLeft={'4px'}>
|
||||
{trade?.tradeType === TradeType.EXACT_INPUT
|
||||
? parsedAmounts[Field.OUTPUT]?.token?.symbol
|
||||
: parsedAmounts[Field.INPUT]?.token?.symbol}
|
||||
? parsedAmounts[Field.OUTPUT]?.currency?.symbol
|
||||
: parsedAmounts[Field.INPUT]?.currency?.symbol}
|
||||
</TYPE.black>
|
||||
)}
|
||||
</RowFixed>
|
||||
@@ -107,7 +107,7 @@ export default function SwapModalFooter({
|
||||
<QuestionHelper text="A portion of each trade (0.30%) goes to liquidity providers as a protocol incentive." />
|
||||
</RowFixed>
|
||||
<TYPE.black fontSize={14}>
|
||||
{realizedLPFee ? realizedLPFee?.toSignificant(6) + ' ' + trade?.inputAmount?.token?.symbol : '-'}
|
||||
{realizedLPFee ? realizedLPFee?.toSignificant(6) + ' ' + trade?.inputAmount?.currency?.symbol : '-'}
|
||||
</TYPE.black>
|
||||
</RowBetween>
|
||||
</AutoColumn>
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
import { Token, TokenAmount } from '@uniswap/sdk'
|
||||
import { Currency, CurrencyAmount } from '@uniswap/sdk'
|
||||
import React, { useContext } from 'react'
|
||||
import { ArrowDown } from 'react-feather'
|
||||
import { Text } from 'rebass'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { Field } from '../../state/swap/actions'
|
||||
import { TYPE } from '../../theme'
|
||||
import { isAddress, shortenAddress } from '../../utils'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
import TokenLogo from '../TokenLogo'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import { TruncatedText } from './styleds'
|
||||
|
||||
export default function SwapModalHeader({
|
||||
tokens,
|
||||
currencies,
|
||||
formattedAmounts,
|
||||
slippageAdjustedAmounts,
|
||||
priceImpactSeverity,
|
||||
independentField
|
||||
independentField,
|
||||
recipient
|
||||
}: {
|
||||
tokens: { [field in Field]?: Token }
|
||||
currencies: { [field in Field]?: Currency }
|
||||
formattedAmounts: { [field in Field]?: string }
|
||||
slippageAdjustedAmounts: { [field in Field]?: TokenAmount }
|
||||
slippageAdjustedAmounts: { [field in Field]?: CurrencyAmount }
|
||||
priceImpactSeverity: number
|
||||
independentField: Field
|
||||
recipient: string | null
|
||||
}) {
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
@@ -32,9 +35,9 @@ export default function SwapModalHeader({
|
||||
{formattedAmounts[Field.INPUT]}
|
||||
</TruncatedText>
|
||||
<RowFixed gap="4px">
|
||||
<TokenLogo address={tokens[Field.INPUT]?.address} size={'24px'} />
|
||||
<CurrencyLogo currency={currencies[Field.INPUT]} size={'24px'} />
|
||||
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
|
||||
{tokens[Field.INPUT]?.symbol}
|
||||
{currencies[Field.INPUT]?.symbol}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
@@ -46,9 +49,9 @@ export default function SwapModalHeader({
|
||||
{formattedAmounts[Field.OUTPUT]}
|
||||
</TruncatedText>
|
||||
<RowFixed gap="4px">
|
||||
<TokenLogo address={tokens[Field.OUTPUT]?.address} size={'24px'} />
|
||||
<CurrencyLogo currency={currencies[Field.OUTPUT]} size={'24px'} />
|
||||
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
|
||||
{tokens[Field.OUTPUT]?.symbol}
|
||||
{currencies[Field.OUTPUT]?.symbol}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
@@ -57,7 +60,7 @@ export default function SwapModalHeader({
|
||||
<TYPE.italic textAlign="left" style={{ width: '100%' }}>
|
||||
{`Output is estimated. You will receive at least `}
|
||||
<b>
|
||||
{slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {tokens[Field.OUTPUT]?.symbol}
|
||||
{slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {currencies[Field.OUTPUT]?.symbol}
|
||||
</b>
|
||||
{' or the transaction will revert.'}
|
||||
</TYPE.italic>
|
||||
@@ -65,12 +68,20 @@ export default function SwapModalHeader({
|
||||
<TYPE.italic textAlign="left" style={{ width: '100%' }}>
|
||||
{`Input is estimated. You will sell at most `}
|
||||
<b>
|
||||
{slippageAdjustedAmounts[Field.INPUT]?.toSignificant(6)} {tokens[Field.INPUT]?.symbol}
|
||||
{slippageAdjustedAmounts[Field.INPUT]?.toSignificant(6)} {currencies[Field.INPUT]?.symbol}
|
||||
</b>
|
||||
{' or the transaction will revert.'}
|
||||
</TYPE.italic>
|
||||
)}
|
||||
</AutoColumn>
|
||||
{recipient !== null ? (
|
||||
<AutoColumn justify="flex-start" gap="sm" style={{ padding: '12px 0 0 0px' }}>
|
||||
<TYPE.main>
|
||||
Output will be sent to{' '}
|
||||
<b title={recipient}>{isAddress(recipient) ? shortenAddress(recipient) : recipient}</b>
|
||||
</TYPE.main>
|
||||
</AutoColumn>
|
||||
) : null}
|
||||
</AutoColumn>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ChevronRight } from 'react-feather'
|
||||
import { Flex } from 'rebass'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { TYPE } from '../../theme'
|
||||
import TokenLogo from '../TokenLogo'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
|
||||
export default memo(function SwapRoute({ trade }: { trade: Trade }) {
|
||||
const theme = useContext(ThemeContext)
|
||||
@@ -24,7 +24,7 @@ export default memo(function SwapRoute({ trade }: { trade: Trade }) {
|
||||
return (
|
||||
<Fragment key={i}>
|
||||
<Flex my="0.5rem" alignItems="center" style={{ flexShrink: 0 }}>
|
||||
<TokenLogo address={token.address} size="1.5rem" />
|
||||
<CurrencyLogo currency={token} size="1.5rem" />
|
||||
<TYPE.black fontSize={14} color={theme.text1} ml="0.5rem">
|
||||
{token.symbol}
|
||||
</TYPE.black>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { Price, Token } from '@uniswap/sdk'
|
||||
import { Currency, Price } from '@uniswap/sdk'
|
||||
import { useContext } from 'react'
|
||||
import { Repeat } from 'react-feather'
|
||||
import { Text } from 'rebass'
|
||||
@@ -8,21 +8,27 @@ import { StyledBalanceMaxMini } from './styleds'
|
||||
|
||||
interface TradePriceProps {
|
||||
price?: Price
|
||||
inputToken?: Token
|
||||
outputToken?: Token
|
||||
inputCurrency?: Currency
|
||||
outputCurrency?: Currency
|
||||
showInverted: boolean
|
||||
setShowInverted: (showInverted: boolean) => void
|
||||
}
|
||||
|
||||
export default function TradePrice({ price, inputToken, outputToken, showInverted, setShowInverted }: TradePriceProps) {
|
||||
export default function TradePrice({
|
||||
price,
|
||||
inputCurrency,
|
||||
outputCurrency,
|
||||
showInverted,
|
||||
setShowInverted
|
||||
}: TradePriceProps) {
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
const formattedPrice = showInverted ? price?.toSignificant(6) : price?.invert()?.toSignificant(6)
|
||||
|
||||
const show = Boolean(inputToken && outputToken)
|
||||
const show = Boolean(inputCurrency && outputCurrency)
|
||||
const label = showInverted
|
||||
? `${outputToken?.symbol} per ${inputToken?.symbol}`
|
||||
: `${inputToken?.symbol} per ${outputToken?.symbol}`
|
||||
? `${outputCurrency?.symbol} per ${inputCurrency?.symbol}`
|
||||
: `${inputCurrency?.symbol} per ${outputCurrency?.symbol}`
|
||||
|
||||
return (
|
||||
<Text
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { TokenAmount } from '@uniswap/sdk'
|
||||
import React from 'react'
|
||||
import { Text } from 'rebass'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { ExternalLink, TYPE } from '../../theme'
|
||||
import { getEtherscanLink } from '../../utils'
|
||||
import Copy from '../AccountDetails/Copy'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { AutoRow, RowBetween } from '../Row'
|
||||
import TokenLogo from '../TokenLogo'
|
||||
|
||||
export function TransferModalHeader({
|
||||
recipient,
|
||||
ENSName,
|
||||
amount
|
||||
}: {
|
||||
recipient: string
|
||||
ENSName: string
|
||||
amount: TokenAmount
|
||||
}) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
return (
|
||||
<AutoColumn gap="lg" style={{ marginTop: '40px' }}>
|
||||
<RowBetween>
|
||||
<Text fontSize={36} fontWeight={500}>
|
||||
{amount?.toSignificant(6)} {amount?.token?.symbol}
|
||||
</Text>
|
||||
<TokenLogo address={amount?.token?.address} size={'30px'} />
|
||||
</RowBetween>
|
||||
<TYPE.darkGray fontSize={20}>To</TYPE.darkGray>
|
||||
{ENSName ? (
|
||||
<AutoColumn gap="lg">
|
||||
<TYPE.blue fontSize={36}>{ENSName}</TYPE.blue>
|
||||
<AutoRow gap="10px">
|
||||
<ExternalLink href={getEtherscanLink(chainId, ENSName, 'address')}>
|
||||
<TYPE.blue fontSize={18}>
|
||||
{recipient?.slice(0, 8)}...{recipient?.slice(34, 42)}↗
|
||||
</TYPE.blue>
|
||||
</ExternalLink>
|
||||
<Copy toCopy={recipient} />
|
||||
</AutoRow>
|
||||
</AutoColumn>
|
||||
) : (
|
||||
<AutoRow gap="10px">
|
||||
<ExternalLink href={getEtherscanLink(chainId, recipient, 'address')}>
|
||||
<TYPE.blue fontSize={36}>
|
||||
{recipient?.slice(0, 6)}...{recipient?.slice(36, 42)}↗
|
||||
</TYPE.blue>
|
||||
</ExternalLink>
|
||||
<Copy toCopy={recipient} />
|
||||
</AutoRow>
|
||||
)}
|
||||
</AutoColumn>
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import styled from 'styled-components'
|
||||
import styled, { css } from 'styled-components'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { Text } from 'rebass'
|
||||
|
||||
@@ -8,17 +8,18 @@ export const Wrapper = styled.div`
|
||||
position: relative;
|
||||
`
|
||||
|
||||
export const ArrowWrapper = styled.div`
|
||||
export const ArrowWrapper = styled.div<{ clickable: boolean }>`
|
||||
padding: 2px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
}
|
||||
${({ clickable }) =>
|
||||
clickable
|
||||
? css`
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
}
|
||||
`
|
||||
: null}
|
||||
`
|
||||
|
||||
export const SectionBreak = styled.div`
|
||||
|
||||
@@ -46,7 +46,16 @@ class MiniRpcProvider implements AsyncSendable {
|
||||
.catch(error => callback(error, null))
|
||||
}
|
||||
|
||||
public readonly request = async (method: string, params?: unknown[] | object): Promise<unknown> => {
|
||||
public readonly request = async (
|
||||
method: string | { method: string; params: unknown[] },
|
||||
params?: unknown[] | object
|
||||
): Promise<unknown> => {
|
||||
if (typeof method !== 'string') {
|
||||
return this.request(method.method, method.params)
|
||||
}
|
||||
if (method === 'eth_chainId') {
|
||||
return `0x${this.chainId.toString(16)}`
|
||||
}
|
||||
const response = await fetch(this.url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
|
||||
@@ -6,7 +6,6 @@ import { PortisConnector } from '@web3-react/portis-connector'
|
||||
import { FortmaticConnector } from './Fortmatic'
|
||||
import { NetworkConnector } from './NetworkConnector'
|
||||
|
||||
const POLLING_INTERVAL = 10000
|
||||
const NETWORK_URL = process.env.REACT_APP_NETWORK_URL
|
||||
const FORMATIC_KEY = process.env.REACT_APP_FORTMATIC_KEY
|
||||
const PORTIS_ID = process.env.REACT_APP_PORTIS_ID
|
||||
@@ -28,7 +27,7 @@ export const walletconnect = new WalletConnectConnector({
|
||||
rpc: { 1: NETWORK_URL },
|
||||
bridge: 'https://bridge.walletconnect.org',
|
||||
qrcode: true,
|
||||
pollingInterval: POLLING_INTERVAL
|
||||
pollingInterval: 15000
|
||||
})
|
||||
|
||||
// mainnet only
|
||||
|
||||
471
src/constants/abis/unisocks.json
Normal file
471
src/constants/abis/unisocks.json
Normal file
@@ -0,0 +1,471 @@
|
||||
[
|
||||
{
|
||||
"name": "Transfer",
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_from",
|
||||
"indexed": true
|
||||
},
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_to",
|
||||
"indexed": true
|
||||
},
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "_tokenId",
|
||||
"indexed": true
|
||||
}
|
||||
],
|
||||
"anonymous": false,
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"name": "Approval",
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_owner",
|
||||
"indexed": true
|
||||
},
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_approved",
|
||||
"indexed": true
|
||||
},
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "_tokenId",
|
||||
"indexed": true
|
||||
}
|
||||
],
|
||||
"anonymous": false,
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"name": "ApprovalForAll",
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_owner",
|
||||
"indexed": true
|
||||
},
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_operator",
|
||||
"indexed": true
|
||||
},
|
||||
{
|
||||
"type": "bool",
|
||||
"name": "_approved",
|
||||
"indexed": false
|
||||
}
|
||||
],
|
||||
"anonymous": false,
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"outputs": [],
|
||||
"inputs": [],
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"name": "tokenURI",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "_tokenId"
|
||||
}
|
||||
],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 22405
|
||||
},
|
||||
{
|
||||
"name": "tokenByIndex",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "_index"
|
||||
}
|
||||
],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 631
|
||||
},
|
||||
{
|
||||
"name": "tokenOfOwnerByIndex",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_owner"
|
||||
},
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "_index"
|
||||
}
|
||||
],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 1248
|
||||
},
|
||||
{
|
||||
"name": "transferFrom",
|
||||
"outputs": [],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_from"
|
||||
},
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_to"
|
||||
},
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "_tokenId"
|
||||
}
|
||||
],
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 259486
|
||||
},
|
||||
{
|
||||
"name": "safeTransferFrom",
|
||||
"outputs": [],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_from"
|
||||
},
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_to"
|
||||
},
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "_tokenId"
|
||||
}
|
||||
],
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"name": "safeTransferFrom",
|
||||
"outputs": [],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_from"
|
||||
},
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_to"
|
||||
},
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "_tokenId"
|
||||
},
|
||||
{
|
||||
"type": "bytes",
|
||||
"name": "_data"
|
||||
}
|
||||
],
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"name": "approve",
|
||||
"outputs": [],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_approved"
|
||||
},
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "_tokenId"
|
||||
}
|
||||
],
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 38422
|
||||
},
|
||||
{
|
||||
"name": "setApprovalForAll",
|
||||
"outputs": [],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_operator"
|
||||
},
|
||||
{
|
||||
"type": "bool",
|
||||
"name": "_approved"
|
||||
}
|
||||
],
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 38016
|
||||
},
|
||||
{
|
||||
"name": "mint",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "bool",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_to"
|
||||
}
|
||||
],
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 182636
|
||||
},
|
||||
{
|
||||
"name": "changeMinter",
|
||||
"outputs": [],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_minter"
|
||||
}
|
||||
],
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 35897
|
||||
},
|
||||
{
|
||||
"name": "changeURI",
|
||||
"outputs": [],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_newURI"
|
||||
}
|
||||
],
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 35927
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 6612
|
||||
},
|
||||
{
|
||||
"name": "symbol",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 6642
|
||||
},
|
||||
{
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 873
|
||||
},
|
||||
{
|
||||
"name": "minter",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 903
|
||||
},
|
||||
{
|
||||
"name": "socks",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "out",
|
||||
"unit": "Socks"
|
||||
}
|
||||
],
|
||||
"inputs": [],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 933
|
||||
},
|
||||
{
|
||||
"name": "newURI",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 963
|
||||
},
|
||||
{
|
||||
"name": "ownerOf",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "arg0"
|
||||
}
|
||||
],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 1126
|
||||
},
|
||||
{
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "arg0"
|
||||
}
|
||||
],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 1195
|
||||
},
|
||||
{
|
||||
"name": "getApproved",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "arg0"
|
||||
}
|
||||
],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 1186
|
||||
},
|
||||
{
|
||||
"name": "isApprovedForAll",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "bool",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "arg0"
|
||||
},
|
||||
{
|
||||
"type": "address",
|
||||
"name": "arg1"
|
||||
}
|
||||
],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 1415
|
||||
},
|
||||
{
|
||||
"name": "supportsInterface",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "bool",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "bytes32",
|
||||
"name": "arg0"
|
||||
}
|
||||
],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 1246
|
||||
}
|
||||
]
|
||||
279
src/constants/abis/weth.json
Normal file
279
src/constants/abis/weth.json
Normal file
@@ -0,0 +1,279 @@
|
||||
[
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "guy",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "wad",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "approve",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "src",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "dst",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "wad",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "wad",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "withdraw",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint8"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "dst",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "wad",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transfer",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [],
|
||||
"name": "deposit",
|
||||
"outputs": [],
|
||||
"payable": true,
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "allowance",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"payable": true,
|
||||
"stateMutability": "payable",
|
||||
"type": "fallback"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "src",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "guy",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "wad",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "src",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "dst",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "wad",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "dst",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "wad",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Deposit",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "src",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "wad",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Withdrawal",
|
||||
"type": "event"
|
||||
}
|
||||
]
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ChainId, JSBI, Percent, Token, WETH, Pair, TokenAmount } from '@uniswap/sdk'
|
||||
import { ChainId, JSBI, Percent, Token, WETH } from '@uniswap/sdk'
|
||||
|
||||
import { fortmatic, injected, portis, walletconnect, walletlink } from '../connectors'
|
||||
import { COMP, DAI, MKR, USDC, USDT } from './tokens/mainnet'
|
||||
|
||||
export const ROUTER_ADDRESS = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D'
|
||||
|
||||
@@ -10,6 +9,12 @@ type ChainTokenList = {
|
||||
readonly [chainId in ChainId]: Token[]
|
||||
}
|
||||
|
||||
export const DAI = new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin')
|
||||
export const USDC = new Token(ChainId.MAINNET, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC', 'USD//C')
|
||||
export const USDT = new Token(ChainId.MAINNET, '0xdAC17F958D2ee523a2206206994597C13D831ec7', 6, 'USDT', 'Tether USD')
|
||||
export const COMP = new Token(ChainId.MAINNET, '0xc00e94Cb662C3520282E6f5717214004A7f26888', 18, 'COMP', 'Compound')
|
||||
export const MKR = new Token(ChainId.MAINNET, '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2', 18, 'MKR', 'Maker')
|
||||
|
||||
const WETH_ONLY: ChainTokenList = {
|
||||
[ChainId.MAINNET]: [WETH[ChainId.MAINNET]],
|
||||
[ChainId.ROPSTEN]: [WETH[ChainId.ROPSTEN]],
|
||||
@@ -36,38 +41,14 @@ export const BASES_TO_TRACK_LIQUIDITY_FOR: ChainTokenList = {
|
||||
[ChainId.MAINNET]: [...WETH_ONLY[ChainId.MAINNET], DAI, USDC, USDT]
|
||||
}
|
||||
|
||||
export const DUMMY_PAIRS_TO_PIN: { readonly [chainId in ChainId]?: Pair[] } = {
|
||||
export const PINNED_PAIRS: { readonly [chainId in ChainId]?: [Token, Token][] } = {
|
||||
[ChainId.MAINNET]: [
|
||||
new Pair(
|
||||
new TokenAmount(
|
||||
new Token(ChainId.MAINNET, '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643', 8, 'cDAI', 'Compound Dai'),
|
||||
'0'
|
||||
),
|
||||
new TokenAmount(
|
||||
new Token(ChainId.MAINNET, '0x39AA39c021dfbaE8faC545936693aC917d5E7563', 8, 'cUSDC', 'Compound USD Coin'),
|
||||
'0'
|
||||
)
|
||||
),
|
||||
new Pair(
|
||||
new TokenAmount(
|
||||
new Token(ChainId.MAINNET, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC', 'USD//C'),
|
||||
'0'
|
||||
),
|
||||
new TokenAmount(
|
||||
new Token(ChainId.MAINNET, '0xdAC17F958D2ee523a2206206994597C13D831ec7', 6, 'USDT', 'Tether USD'),
|
||||
'0'
|
||||
)
|
||||
),
|
||||
new Pair(
|
||||
new TokenAmount(
|
||||
new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin'),
|
||||
'0'
|
||||
),
|
||||
new TokenAmount(
|
||||
new Token(ChainId.MAINNET, '0xdAC17F958D2ee523a2206206994597C13D831ec7', 6, 'USDT', 'Tether USD'),
|
||||
'0'
|
||||
)
|
||||
)
|
||||
[
|
||||
new Token(ChainId.MAINNET, '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643', 8, 'cDAI', 'Compound Dai'),
|
||||
new Token(ChainId.MAINNET, '0x39AA39c021dfbaE8faC545936693aC917d5E7563', 8, 'cUSDC', 'Compound USD Coin')
|
||||
],
|
||||
[USDC, USDT],
|
||||
[DAI, USDT]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -166,3 +147,7 @@ export const BLOCKED_PRICE_IMPACT_NON_EXPERT: Percent = new Percent(JSBI.BigInt(
|
||||
// used to ensure the user doesn't send so much ETH so they end up with <.01
|
||||
export const MIN_ETH: JSBI = JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(16)) // .01 ETH
|
||||
export const BETTER_TRADE_LINK_THRESHOLD = new Percent(JSBI.BigInt(75), JSBI.BigInt(10000))
|
||||
|
||||
// the Uniswap Default token list lives here
|
||||
export const DEFAULT_TOKEN_LIST_URL =
|
||||
'https://unpkg.com/@uniswap/default-token-list@latest/uniswap-default.tokenlist.json'
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { ChainId, Token, WETH } from '@uniswap/sdk'
|
||||
import KOVAN_TOKENS from './kovan'
|
||||
import MAINNET_TOKENS from './mainnet'
|
||||
import RINKEBY_TOKENS from './rinkeby'
|
||||
import ROPSTEN_TOKENS from './ropsten'
|
||||
|
||||
type AllTokens = Readonly<{ [chainId in ChainId]: Readonly<{ [tokenAddress: string]: Token }> }>
|
||||
export const ALL_TOKENS: AllTokens = [
|
||||
// WETH on all chains
|
||||
...Object.values(WETH),
|
||||
// chain-specific tokens
|
||||
...MAINNET_TOKENS,
|
||||
...RINKEBY_TOKENS,
|
||||
...KOVAN_TOKENS,
|
||||
...ROPSTEN_TOKENS
|
||||
]
|
||||
// remap WETH to ETH
|
||||
.map(token => {
|
||||
if (token.equals(WETH[token.chainId])) {
|
||||
;(token as any).symbol = 'ETH'
|
||||
;(token as any).name = 'Ether'
|
||||
}
|
||||
return token
|
||||
})
|
||||
// put into an object
|
||||
.reduce<AllTokens>(
|
||||
(tokenMap, token) => {
|
||||
if (tokenMap[token.chainId][token.address] !== undefined) throw Error('Duplicate tokens.')
|
||||
return {
|
||||
...tokenMap,
|
||||
[token.chainId]: {
|
||||
...tokenMap[token.chainId],
|
||||
[token.address]: token
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
[ChainId.MAINNET]: {},
|
||||
[ChainId.RINKEBY]: {},
|
||||
[ChainId.GÖRLI]: {},
|
||||
[ChainId.ROPSTEN]: {},
|
||||
[ChainId.KOVAN]: {}
|
||||
}
|
||||
)
|
||||
@@ -1,6 +0,0 @@
|
||||
import { Token, ChainId } from '@uniswap/sdk'
|
||||
|
||||
export default [
|
||||
new Token(ChainId.KOVAN, '0x4F96Fe3b7A6Cf9725f59d353F723c1bDb64CA6Aa', 18, 'DAI', 'Dai Stablecoin'),
|
||||
new Token(ChainId.KOVAN, '0xAaF64BFCC32d0F15873a02163e7E500671a4ffcD', 18, 'MKR', 'Maker')
|
||||
]
|
||||
@@ -1,132 +0,0 @@
|
||||
import { Token, ChainId } from '@uniswap/sdk'
|
||||
|
||||
export const DAI = new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin')
|
||||
export const USDC = new Token(ChainId.MAINNET, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC', 'USD//C')
|
||||
export const USDT = new Token(ChainId.MAINNET, '0xdAC17F958D2ee523a2206206994597C13D831ec7', 6, 'USDT', 'Tether USD')
|
||||
export const COMP = new Token(ChainId.MAINNET, '0xc00e94Cb662C3520282E6f5717214004A7f26888', 18, 'COMP', 'Compound')
|
||||
export const MKR = new Token(ChainId.MAINNET, '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2', 18, 'MKR', 'Maker')
|
||||
|
||||
export default [
|
||||
new Token(ChainId.MAINNET, '0xB6eD7644C69416d67B522e20bC294A9a9B405B31', 8, '0xBTC', '0xBitcoin Token'),
|
||||
new Token(ChainId.MAINNET, '0xfC1E690f61EFd961294b3e1Ce3313fBD8aa4f85d', 18, 'aDAI', 'Aave Interest bearing DAI'),
|
||||
new Token(ChainId.MAINNET, '0x737F98AC8cA59f2C68aD658E3C3d8C8963E40a4c', 18, 'AMN', 'Amon'),
|
||||
new Token(ChainId.MAINNET, '0xD46bA6D942050d489DBd938a2C909A5d5039A161', 9, 'AMPL', 'Ampleforth'),
|
||||
new Token(ChainId.MAINNET, '0xcD62b1C403fa761BAadFC74C525ce2B51780b184', 18, 'ANJ', 'Aragon Network Juror'),
|
||||
new Token(ChainId.MAINNET, '0x960b236A07cf122663c4303350609A66A7B288C0', 18, 'ANT', 'Aragon Network Token'),
|
||||
new Token(ChainId.MAINNET, '0x27054b13b1B798B345b591a4d22e6562d47eA75a', 4, 'AST', 'AirSwap Token'),
|
||||
new Token(ChainId.MAINNET, '0xBA11D00c5f74255f56a5E366F4F77f5A186d7f55', 18, 'BAND', 'BandToken'),
|
||||
new Token(ChainId.MAINNET, '0x0D8775F648430679A709E98d2b0Cb6250d2887EF', 18, 'BAT', 'Basic Attention Token'),
|
||||
new Token(ChainId.MAINNET, '0xba100000625a3754423978a60c9317c58a424e3D', 18, 'BAL', 'Balancer'),
|
||||
new Token(ChainId.MAINNET, '0x107c4504cd79C5d2696Ea0030a8dD4e92601B82e', 18, 'BLT', 'Bloom Token'),
|
||||
new Token(ChainId.MAINNET, '0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C', 18, 'BNT', 'Bancor Network Token'),
|
||||
new Token(ChainId.MAINNET, '0x0327112423F3A68efdF1fcF402F6c5CB9f7C33fd', 18, 'BTC++', 'PieDAO BTC++'),
|
||||
new Token(ChainId.MAINNET, '0x4F9254C83EB525f9FCf346490bbb3ed28a81C667', 18, 'CELR', 'CelerToken'),
|
||||
new Token(ChainId.MAINNET, '0xF5DCe57282A584D2746FaF1593d3121Fcac444dC', 8, 'cSAI', 'Compound Dai'),
|
||||
new Token(ChainId.MAINNET, '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643', 8, 'cDAI', 'Compound Dai'),
|
||||
new Token(ChainId.MAINNET, '0x39AA39c021dfbaE8faC545936693aC917d5E7563', 8, 'cUSDC', 'Compound USD Coin'),
|
||||
new Token(ChainId.MAINNET, '0xaaAEBE6Fe48E54f431b0C390CfaF0b017d09D42d', 4, 'CEL', 'Celsius'),
|
||||
new Token(ChainId.MAINNET, '0x06AF07097C9Eeb7fD685c692751D5C66dB49c215', 18, 'CHAI', 'Chai'),
|
||||
COMP,
|
||||
new Token(ChainId.MAINNET, '0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359', 18, 'SAI', 'Dai Stablecoin v1.0 (SAI)'),
|
||||
DAI,
|
||||
new Token(ChainId.MAINNET, '0x0Cf0Ee63788A0849fE5297F3407f701E122cC023', 18, 'DATA', 'Streamr DATAcoin'),
|
||||
new Token(ChainId.MAINNET, '0xE0B7927c4aF23765Cb51314A0E0521A9645F0E2A', 9, 'DGD', 'DigixDAO'),
|
||||
new Token(ChainId.MAINNET, '0x4f3AfEC4E5a3F2A6a1A411DEF7D7dFe50eE057bF', 9, 'DGX', 'Digix Gold Token'),
|
||||
new Token(
|
||||
ChainId.MAINNET,
|
||||
'0xc719d010B63E5bbF2C0551872CD5316ED26AcD83',
|
||||
18,
|
||||
'DIP',
|
||||
'Decentralized Insurance Protocol'
|
||||
),
|
||||
new Token(ChainId.MAINNET, '0xC0F9bD5Fa5698B6505F643900FFA515Ea5dF54A9', 18, 'DONUT', 'Donut'),
|
||||
new Token(ChainId.MAINNET, '0x86FADb80d8D2cff3C3680819E4da99C10232Ba0F', 18, 'EBASE', 'EURBASE Stablecoin'),
|
||||
new Token(ChainId.MAINNET, '0xF629cBd94d3791C9250152BD8dfBDF380E2a3B9c', 18, 'ENJ', 'Enjin Coin'),
|
||||
new Token(ChainId.MAINNET, '0x06f65b8CfCb13a9FE37d836fE9708dA38Ecb29B2', 18, 'FAME', 'SAINT FAME: Genesis Shirt'),
|
||||
new Token(ChainId.MAINNET, '0x4946Fcea7C692606e8908002e55A582af44AC121', 18, 'FOAM', 'FOAM Token'),
|
||||
new Token(ChainId.MAINNET, '0x419D0d8BdD9aF5e606Ae2232ed285Aff190E711b', 8, 'FUN', 'FunFair'),
|
||||
new Token(ChainId.MAINNET, '0x4a57E687b9126435a9B19E4A802113e266AdeBde', 18, 'FXC', 'Flexacoin'),
|
||||
new Token(ChainId.MAINNET, '0x543Ff227F64Aa17eA132Bf9886cAb5DB55DCAddf', 18, 'GEN', 'DAOstack'),
|
||||
new Token(ChainId.MAINNET, '0x6810e776880C02933D47DB1b9fc05908e5386b96', 18, 'GNO', 'Gnosis Token'),
|
||||
new Token(ChainId.MAINNET, '0x12B19D3e2ccc14Da04FAe33e63652ce469b3F2FD', 12, 'GRID', 'GRID Token'),
|
||||
new Token(ChainId.MAINNET, '0x0000000000b3F879cb30FE243b4Dfee438691c04', 2, 'GST2', 'Gastoken.io'),
|
||||
new Token(ChainId.MAINNET, '0xF1290473E210b2108A85237fbCd7b6eb42Cc654F', 18, 'HEDG', 'HedgeTrade'),
|
||||
new Token(ChainId.MAINNET, '0x6c6EE5e31d828De241282B9606C8e98Ea48526E2', 18, 'HOT', 'HoloToken'),
|
||||
new Token(ChainId.MAINNET, '0x493C57C4763932315A328269E1ADaD09653B9081', 18, 'iDAI', 'Fulcrum DAI iToken'),
|
||||
new Token(ChainId.MAINNET, '0x14094949152EDDBFcd073717200DA82fEd8dC960', 18, 'iSAI', 'Fulcrum SAI iToken '),
|
||||
new Token(ChainId.MAINNET, '0x6fB3e0A217407EFFf7Ca062D46c26E5d60a14d69', 18, 'IOTX', 'IoTeX Network'),
|
||||
new Token(ChainId.MAINNET, '0x4Cd988AfBad37289BAAf53C13e98E2BD46aAEa8c', 18, 'KEY', 'KEY'),
|
||||
new Token(ChainId.MAINNET, '0xdd974D5C2e2928deA5F71b9825b8b646686BD200', 18, 'KNC', 'Kyber Network Crystal'),
|
||||
new Token(ChainId.MAINNET, '0x514910771AF9Ca656af840dff83E8264EcF986CA', 18, 'LINK', 'ChainLink Token'),
|
||||
new Token(ChainId.MAINNET, '0xBBbbCA6A901c926F240b89EacB641d8Aec7AEafD', 18, 'LRC', 'LoopringCoin V2'),
|
||||
new Token(ChainId.MAINNET, '0x80fB784B7eD66730e8b1DBd9820aFD29931aab03', 18, 'LEND', 'EthLend Token'),
|
||||
new Token(ChainId.MAINNET, '0xA4e8C3Ec456107eA67d3075bF9e3DF3A75823DB0', 18, 'LOOM', 'LoomToken'),
|
||||
new Token(ChainId.MAINNET, '0x58b6A8A3302369DAEc383334672404Ee733aB239', 18, 'LPT', 'Livepeer Token'),
|
||||
new Token(ChainId.MAINNET, '0xD29F0b5b3F50b07Fe9a9511F7d86F4f4bAc3f8c4', 18, 'LQD', 'Liquidity.Network Token'),
|
||||
new Token(ChainId.MAINNET, '0x0F5D2fB29fb7d3CFeE444a200298f468908cC942', 18, 'MANA', 'Decentraland MANA'),
|
||||
new Token(ChainId.MAINNET, '0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0', 18, 'MATIC', 'Matic Token'),
|
||||
new Token(ChainId.MAINNET, '0x8888889213DD4dA823EbDD1e235b09590633C150', 18, 'MBC', 'Marblecoin'),
|
||||
new Token(ChainId.MAINNET, '0xd15eCDCF5Ea68e3995b2D0527A0aE0a3258302F8', 18, 'MCX', 'MachiX Token'),
|
||||
new Token(ChainId.MAINNET, '0xa3d58c4E56fedCae3a7c43A725aeE9A71F0ece4e', 18, 'MET', 'Metronome'),
|
||||
new Token(ChainId.MAINNET, '0x80f222a749a2e18Eb7f676D371F19ad7EFEEe3b7', 18, 'MGN', 'Magnolia Token'),
|
||||
MKR,
|
||||
new Token(ChainId.MAINNET, '0xec67005c4E498Ec7f55E092bd1d35cbC47C91892', 18, 'MLN', 'Melon Token'),
|
||||
new Token(ChainId.MAINNET, '0x957c30aB0426e0C93CD8241E2c60392d08c6aC8e', 0, 'MOD', 'Modum Token'),
|
||||
new Token(ChainId.MAINNET, '0xe2f2a5C287993345a840Db3B0845fbC70f5935a5', 18, 'mUSD', 'mStable USD'),
|
||||
new Token(ChainId.MAINNET, '0xB62132e35a6c13ee1EE0f84dC5d40bad8d815206', 18, 'NEXO', 'Nexo'),
|
||||
new Token(ChainId.MAINNET, '0x1776e1F26f98b1A5dF9cD347953a26dd3Cb46671', 18, 'NMR', 'Numeraire'),
|
||||
new Token(ChainId.MAINNET, '0x985dd3D42De1e256d09e1c10F112bCCB8015AD41', 18, 'OCEAN', 'OceanToken'),
|
||||
new Token(ChainId.MAINNET, '0x4575f41308EC1483f3d399aa9a2826d74Da13Deb', 18, 'OXT', 'Orchid'),
|
||||
new Token(ChainId.MAINNET, '0xD56daC73A4d6766464b38ec6D91eB45Ce7457c44', 18, 'PAN', 'Panvala pan'),
|
||||
new Token(ChainId.MAINNET, '0x8E870D67F660D95d5be530380D0eC0bd388289E1', 18, 'PAX', 'PAX'),
|
||||
new Token(ChainId.MAINNET, '0x45804880De22913dAFE09f4980848ECE6EcbAf78', 18, 'PAXG', 'Paxos Gold'),
|
||||
new Token(ChainId.MAINNET, '0x93ED3FBe21207Ec2E8f2d3c3de6e058Cb73Bc04d', 18, 'PNK', 'Pinakion'),
|
||||
new Token(ChainId.MAINNET, '0x6758B7d441a9739b98552B373703d8d3d14f9e62', 18, 'POA20', 'POA ERC20 on Foundation'),
|
||||
new Token(ChainId.MAINNET, '0x687BfC3E73f6af55F0CccA8450114D107E781a0e', 18, 'QCH', 'QChi'),
|
||||
new Token(ChainId.MAINNET, '0x4a220E6096B25EADb88358cb44068A3248254675', 18, 'QNT', 'Quant'),
|
||||
new Token(ChainId.MAINNET, '0x99ea4dB9EE77ACD40B119BD1dC4E33e1C070b80d', 18, 'QSP', 'Quantstamp Token'),
|
||||
new Token(ChainId.MAINNET, '0xF970b8E36e23F7fC3FD752EeA86f8Be8D83375A6', 18, 'RCN', 'Ripio Credit Network Token'),
|
||||
new Token(ChainId.MAINNET, '0x255Aa6DF07540Cb5d3d297f0D0D4D84cb52bc8e6', 18, 'RDN', 'Raiden Token'),
|
||||
new Token(ChainId.MAINNET, '0x408e41876cCCDC0F92210600ef50372656052a38', 18, 'REN', 'Republic Token'),
|
||||
new Token(ChainId.MAINNET, '0x459086F2376525BdCebA5bDDA135e4E9d3FeF5bf', 8, 'renBCH', 'renBCH'),
|
||||
new Token(ChainId.MAINNET, '0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D', 8, 'renBTC', 'renBTC'),
|
||||
new Token(ChainId.MAINNET, '0x1C5db575E2Ff833E46a2E9864C22F4B22E0B37C2', 8, 'renZEC', 'renZEC'),
|
||||
new Token(ChainId.MAINNET, '0x1985365e9f78359a9B6AD760e32412f4a445E862', 18, 'REP', 'Reputation'),
|
||||
new Token(ChainId.MAINNET, '0x9469D013805bFfB7D3DEBe5E7839237e535ec483', 18, 'RING', 'Darwinia Network Native Token'),
|
||||
new Token(ChainId.MAINNET, '0x607F4C5BB672230e8672085532f7e901544a7375', 9, 'RLC', 'iEx.ec Network Token'),
|
||||
new Token(ChainId.MAINNET, '0xB4EFd85c19999D84251304bDA99E90B92300Bd93', 18, 'RPL', 'Rocket Pool'),
|
||||
new Token(ChainId.MAINNET, '0x4156D3342D5c385a87D264F90653733592000581', 8, 'SALT', 'Salt'),
|
||||
new Token(ChainId.MAINNET, '0x7C5A0CE9267ED19B22F8cae653F198e3E8daf098', 18, 'SAN', 'SANtiment network token'),
|
||||
new Token(ChainId.MAINNET, '0x5e74C9036fb86BD7eCdcb084a0673EFc32eA31cb', 18, 'sETH', 'Synth sETH'),
|
||||
new Token(ChainId.MAINNET, '0x3A9FfF453d50D4Ac52A6890647b823379ba36B9E', 18, 'SHUF', 'Shuffle.Monster V3'),
|
||||
new Token(ChainId.MAINNET, '0x744d70FDBE2Ba4CF95131626614a1763DF805B9E', 18, 'SNT', 'Status Network Token'),
|
||||
new Token(ChainId.MAINNET, '0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F', 18, 'SNX', 'Synthetix Network Token'),
|
||||
new Token(ChainId.MAINNET, '0x23B608675a2B2fB1890d3ABBd85c5775c51691d5', 18, 'SOCKS', 'Unisocks Edition 0'),
|
||||
new Token(ChainId.MAINNET, '0x42d6622deCe394b54999Fbd73D108123806f6a18', 18, 'SPANK', 'SPANK'),
|
||||
new Token(ChainId.MAINNET, '0x0Ae055097C6d159879521C384F1D2123D1f195e6', 18, 'STAKE', 'STAKE'),
|
||||
new Token(ChainId.MAINNET, '0xB64ef51C888972c908CFacf59B47C1AfBC0Ab8aC', 8, 'STORJ', 'StorjToken'),
|
||||
new Token(ChainId.MAINNET, '0x57Ab1ec28D129707052df4dF418D58a2D46d5f51', 18, 'sUSD', 'Synth sUSD'),
|
||||
new Token(ChainId.MAINNET, '0x261EfCdD24CeA98652B9700800a13DfBca4103fF', 18, 'sXAU', 'Synth sXAU'),
|
||||
new Token(ChainId.MAINNET, '0x8CE9137d39326AD0cD6491fb5CC0CbA0e089b6A9', 18, 'SXP', 'Swipe'),
|
||||
new Token(ChainId.MAINNET, '0x00006100F7090010005F1bd7aE6122c3C2CF0090', 18, 'TAUD', 'TrueAUD'),
|
||||
new Token(ChainId.MAINNET, '0x00000100F2A2bd000715001920eB70D229700085', 18, 'TCAD', 'TrueCAD'),
|
||||
new Token(ChainId.MAINNET, '0x00000000441378008EA67F4284A57932B1c000a5', 18, 'TGBP', 'TrueGBP'),
|
||||
new Token(ChainId.MAINNET, '0x0000852600CEB001E08e00bC008be620d60031F2', 18, 'THKD', 'TrueHKD'),
|
||||
new Token(ChainId.MAINNET, '0xaAAf91D9b90dF800Df4F55c205fd6989c977E73a', 8, 'TKN', 'Monolith TKN'),
|
||||
new Token(ChainId.MAINNET, '0x0Ba45A8b5d5575935B8158a88C631E9F9C95a2e5', 18, 'TRB', 'Tellor Tributes'),
|
||||
new Token(ChainId.MAINNET, '0xCb94be6f13A1182E4A4B6140cb7bf2025d28e41B', 6, 'TRST', 'Trustcoin'),
|
||||
new Token(ChainId.MAINNET, '0x2C537E5624e4af88A7ae4060C022609376C8D0EB', 6, 'TRYB', 'BiLira'),
|
||||
new Token(ChainId.MAINNET, '0x0000000000085d4780B73119b644AE5ecd22b376', 18, 'TUSD', 'TrueUSD'),
|
||||
new Token(ChainId.MAINNET, '0x8400D94A5cb0fa0D041a3788e395285d61c9ee5e', 8, 'UBT', 'UniBright'),
|
||||
new Token(ChainId.MAINNET, '0x04Fa0d235C4abf4BcF4787aF4CF447DE572eF828', 18, 'UMA', 'UMA Voting Token v1'),
|
||||
USDC,
|
||||
new Token(ChainId.MAINNET, '0xA4Bdb11dc0a2bEC88d24A3aa1E6Bb17201112eBe', 6, 'USDS', 'StableUSD'),
|
||||
USDT,
|
||||
new Token(ChainId.MAINNET, '0xeb269732ab75A6fD61Ea60b06fE994cD32a83549', 18, 'USDx', 'dForce'),
|
||||
new Token(ChainId.MAINNET, '0x8f3470A7388c05eE4e7AF3d01D8C722b0FF52374', 18, 'VERI', 'Veritaseum'),
|
||||
new Token(ChainId.MAINNET, '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', 8, 'WBTC', 'Wrapped BTC'),
|
||||
new Token(ChainId.MAINNET, '0x09fE5f0236F0Ea5D930197DCE254d77B04128075', 18, 'WCK', 'Wrapped CryptoKitties'),
|
||||
new Token(ChainId.MAINNET, '0xB4272071eCAdd69d933AdcD19cA99fe80664fc08', 18, 'XCHF', 'CryptoFranc'),
|
||||
new Token(ChainId.MAINNET, '0x0f7F961648aE6Db43C75663aC7E5414Eb79b5704', 18, 'XIO', 'XIO Network'),
|
||||
new Token(ChainId.MAINNET, '0xE41d2489571d322189246DaFA5ebDe1F4699F498', 18, 'ZRX', '0x Protocol Token')
|
||||
]
|
||||
@@ -1,6 +0,0 @@
|
||||
import { Token, ChainId } from '@uniswap/sdk'
|
||||
|
||||
export default [
|
||||
new Token(ChainId.RINKEBY, '0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735', 18, 'DAI', 'Dai Stablecoin'),
|
||||
new Token(ChainId.RINKEBY, '0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85', 18, 'MKR', 'Maker')
|
||||
]
|
||||
@@ -1,3 +0,0 @@
|
||||
import { Token, ChainId } from '@uniswap/sdk'
|
||||
|
||||
export default [new Token(ChainId.ROPSTEN, '0xaD6D458402F60fD3Bd25163575031ACDce07538D', 18, 'DAI', 'Dai Stablecoin')]
|
||||
@@ -1,18 +1,33 @@
|
||||
import { Token, TokenAmount, Pair } from '@uniswap/sdk'
|
||||
import { TokenAmount, Pair, Currency } from '@uniswap/sdk'
|
||||
import { useMemo } from 'react'
|
||||
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
|
||||
import { Interface } from '@ethersproject/abi'
|
||||
import { useActiveWeb3React } from '../hooks'
|
||||
|
||||
import { useMultipleContractSingleData } from '../state/multicall/hooks'
|
||||
import { wrappedCurrency } from '../utils/wrappedCurrency'
|
||||
|
||||
const PAIR_INTERFACE = new Interface(IUniswapV2PairABI)
|
||||
|
||||
/*
|
||||
* if loading, return undefined
|
||||
* if no pair created yet, return null
|
||||
* if pair already created (even if 0 reserves), return pair
|
||||
*/
|
||||
export function usePairs(tokens: [Token | undefined, Token | undefined][]): (undefined | Pair | null)[] {
|
||||
export enum PairState {
|
||||
LOADING,
|
||||
NOT_EXISTS,
|
||||
EXISTS,
|
||||
INVALID
|
||||
}
|
||||
|
||||
export function usePairs(currencies: [Currency | undefined, Currency | undefined][]): [PairState, Pair | null][] {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
|
||||
const tokens = useMemo(
|
||||
() =>
|
||||
currencies.map(([currencyA, currencyB]) => [
|
||||
wrappedCurrency(currencyA, chainId),
|
||||
wrappedCurrency(currencyB, chainId)
|
||||
]),
|
||||
[chainId, currencies]
|
||||
)
|
||||
|
||||
const pairAddresses = useMemo(
|
||||
() =>
|
||||
tokens.map(([tokenA, tokenB]) => {
|
||||
@@ -29,15 +44,19 @@ export function usePairs(tokens: [Token | undefined, Token | undefined][]): (und
|
||||
const tokenA = tokens[i][0]
|
||||
const tokenB = tokens[i][1]
|
||||
|
||||
if (loading || !tokenA || !tokenB) return undefined
|
||||
if (!reserves) return null
|
||||
if (loading) return [PairState.LOADING, null]
|
||||
if (!tokenA || !tokenB || tokenA.equals(tokenB)) return [PairState.INVALID, null]
|
||||
if (!reserves) return [PairState.NOT_EXISTS, null]
|
||||
const { reserve0, reserve1 } = reserves
|
||||
const [token0, token1] = tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA]
|
||||
return new Pair(new TokenAmount(token0, reserve0.toString()), new TokenAmount(token1, reserve1.toString()))
|
||||
return [
|
||||
PairState.EXISTS,
|
||||
new Pair(new TokenAmount(token0, reserve0.toString()), new TokenAmount(token1, reserve1.toString()))
|
||||
]
|
||||
})
|
||||
}, [results, tokens])
|
||||
}
|
||||
|
||||
export function usePair(tokenA?: Token, tokenB?: Token): undefined | Pair | null {
|
||||
export function usePair(tokenA?: Currency, tokenB?: Currency): [PairState, Pair | null] {
|
||||
return usePairs([[tokenA, tokenB]])[0]
|
||||
}
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
import { JSBI, Pair, Percent, Route, Token, TokenAmount, Trade, TradeType, WETH } from '@uniswap/sdk'
|
||||
import { AddressZero } from '@ethersproject/constants'
|
||||
import {
|
||||
BigintIsh,
|
||||
Currency,
|
||||
CurrencyAmount,
|
||||
currencyEquals,
|
||||
ETHER,
|
||||
JSBI,
|
||||
Pair,
|
||||
Percent,
|
||||
Route,
|
||||
Token,
|
||||
TokenAmount,
|
||||
Trade,
|
||||
TradeType,
|
||||
WETH
|
||||
} from '@uniswap/sdk'
|
||||
import { useMemo } from 'react'
|
||||
import { useActiveWeb3React } from '../hooks'
|
||||
import { useAllTokens } from '../hooks/Tokens'
|
||||
@@ -6,7 +22,6 @@ import { useV1FactoryContract } from '../hooks/useContract'
|
||||
import { Version } from '../hooks/useToggledVersion'
|
||||
import { NEVER_RELOAD, useSingleCallResult, useSingleContractMultipleData } from '../state/multicall/hooks'
|
||||
import { useETHBalances, useTokenBalance, useTokenBalances } from '../state/wallet/hooks'
|
||||
import { AddressZero } from '@ethersproject/constants'
|
||||
|
||||
export function useV1ExchangeAddress(tokenAddress?: string): string | undefined {
|
||||
const contract = useV1FactoryContract()
|
||||
@@ -15,18 +30,25 @@ export function useV1ExchangeAddress(tokenAddress?: string): string | undefined
|
||||
return useSingleCallResult(contract, 'getExchange', inputs)?.result?.[0]
|
||||
}
|
||||
|
||||
class MockV1Pair extends Pair {}
|
||||
export class MockV1Pair extends Pair {
|
||||
constructor(etherAmount: BigintIsh, tokenAmount: TokenAmount) {
|
||||
super(tokenAmount, new TokenAmount(WETH[tokenAmount.token.chainId], etherAmount))
|
||||
}
|
||||
}
|
||||
|
||||
function useMockV1Pair(token?: Token): MockV1Pair | undefined {
|
||||
const isWETH: boolean = token && WETH[token.chainId] ? token.equals(WETH[token.chainId]) : false
|
||||
function useMockV1Pair(inputCurrency?: Currency): MockV1Pair | undefined {
|
||||
const token = inputCurrency instanceof Token ? inputCurrency : undefined
|
||||
|
||||
const isWETH = Boolean(token && token.equals(WETH[token.chainId]))
|
||||
const v1PairAddress = useV1ExchangeAddress(isWETH ? undefined : token?.address)
|
||||
const tokenBalance = useTokenBalance(v1PairAddress, token)
|
||||
const ETHBalance = useETHBalances([v1PairAddress])[v1PairAddress ?? '']
|
||||
|
||||
return tokenBalance && ETHBalance && token
|
||||
? new MockV1Pair(tokenBalance, new TokenAmount(WETH[token.chainId], ETHBalance.toString()))
|
||||
: undefined
|
||||
return useMemo(
|
||||
() =>
|
||||
token && tokenBalance && ETHBalance && inputCurrency ? new MockV1Pair(ETHBalance.raw, tokenBalance) : undefined,
|
||||
[ETHBalance, inputCurrency, token, tokenBalance]
|
||||
)
|
||||
}
|
||||
|
||||
// returns all v1 exchange addresses in the user's token list
|
||||
@@ -41,8 +63,7 @@ export function useAllTokenV1Exchanges(): { [exchangeAddress: string]: Token } {
|
||||
() =>
|
||||
data?.reduce<{ [exchangeAddress: string]: Token }>((memo, { result }, ix) => {
|
||||
if (result?.[0] && result[0] !== AddressZero) {
|
||||
const token = allTokens[args[ix][0]]
|
||||
memo[result[0]] = token
|
||||
memo[result[0]] = allTokens[args[ix][0]]
|
||||
}
|
||||
return memo
|
||||
}, {}) ?? {},
|
||||
@@ -56,13 +77,13 @@ export function useUserHasLiquidityInAllTokens(): boolean | undefined {
|
||||
|
||||
const exchanges = useAllTokenV1Exchanges()
|
||||
|
||||
const fakeLiquidityTokens = useMemo(
|
||||
const v1ExchangeLiquidityTokens = useMemo(
|
||||
() =>
|
||||
chainId ? Object.keys(exchanges).map(address => new Token(chainId, address, 18, 'UNI-V1', 'Uniswap V1')) : [],
|
||||
[chainId, exchanges]
|
||||
)
|
||||
|
||||
const balances = useTokenBalances(account ?? undefined, fakeLiquidityTokens)
|
||||
const balances = useTokenBalances(account ?? undefined, v1ExchangeLiquidityTokens)
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
@@ -79,39 +100,39 @@ export function useUserHasLiquidityInAllTokens(): boolean | undefined {
|
||||
*/
|
||||
export function useV1Trade(
|
||||
isExactIn?: boolean,
|
||||
inputToken?: Token,
|
||||
outputToken?: Token,
|
||||
exactAmount?: TokenAmount
|
||||
inputCurrency?: Currency,
|
||||
outputCurrency?: Currency,
|
||||
exactAmount?: CurrencyAmount
|
||||
): Trade | undefined {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
|
||||
// get the mock v1 pairs
|
||||
const inputPair = useMockV1Pair(inputToken)
|
||||
const outputPair = useMockV1Pair(outputToken)
|
||||
const inputPair = useMockV1Pair(inputCurrency)
|
||||
const outputPair = useMockV1Pair(outputCurrency)
|
||||
|
||||
const inputIsWETH = (inputToken && chainId && WETH[chainId] && inputToken.equals(WETH[chainId])) ?? false
|
||||
const outputIsWETH = (outputToken && chainId && WETH[chainId] && outputToken.equals(WETH[chainId])) ?? false
|
||||
const inputIsETH = inputCurrency === ETHER
|
||||
const outputIsETH = outputCurrency === ETHER
|
||||
|
||||
// construct a direct or through ETH v1 route
|
||||
let pairs: Pair[] = []
|
||||
if (inputIsWETH && outputPair) {
|
||||
if (inputIsETH && outputPair) {
|
||||
pairs = [outputPair]
|
||||
} else if (outputIsWETH && inputPair) {
|
||||
} else if (outputIsETH && inputPair) {
|
||||
pairs = [inputPair]
|
||||
}
|
||||
// if neither are WETH, it's token-to-token (if they both exist)
|
||||
// if neither are ETH, it's token-to-token (if they both exist)
|
||||
else if (inputPair && outputPair) {
|
||||
pairs = [inputPair, outputPair]
|
||||
}
|
||||
|
||||
const route = inputToken && pairs && pairs.length > 0 && new Route(pairs, inputToken)
|
||||
const route = inputCurrency && pairs && pairs.length > 0 && new Route(pairs, inputCurrency, outputCurrency)
|
||||
let v1Trade: Trade | undefined
|
||||
try {
|
||||
v1Trade =
|
||||
route && exactAmount
|
||||
? new Trade(route, exactAmount, isExactIn ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT)
|
||||
: undefined
|
||||
} catch {}
|
||||
} catch (error) {
|
||||
console.error('Failed to create V1 trade', error)
|
||||
}
|
||||
return v1Trade
|
||||
}
|
||||
|
||||
@@ -125,14 +146,13 @@ export function getTradeVersion(trade?: Trade): Version | undefined {
|
||||
// returns the v1 exchange against which a trade should be executed
|
||||
export function useV1TradeExchangeAddress(trade: Trade | undefined): string | undefined {
|
||||
const tokenAddress: string | undefined = useMemo(() => {
|
||||
const tradeVersion = getTradeVersion(trade)
|
||||
const isV1 = tradeVersion === Version.v1
|
||||
return isV1
|
||||
? trade &&
|
||||
WETH[trade.inputAmount.token.chainId] &&
|
||||
trade.inputAmount.token.equals(WETH[trade.inputAmount.token.chainId])
|
||||
? trade.outputAmount.token.address
|
||||
: trade?.inputAmount?.token?.address
|
||||
if (!trade) return undefined
|
||||
const isV1 = getTradeVersion(trade) === Version.v1
|
||||
if (!isV1) return undefined
|
||||
return trade.inputAmount instanceof TokenAmount
|
||||
? trade.inputAmount.token.address
|
||||
: trade.outputAmount instanceof TokenAmount
|
||||
? trade.outputAmount.token.address
|
||||
: undefined
|
||||
}, [trade])
|
||||
return useV1ExchangeAddress(tokenAddress)
|
||||
@@ -140,7 +160,8 @@ export function useV1TradeExchangeAddress(trade: Trade | undefined): string | un
|
||||
|
||||
const ZERO_PERCENT = new Percent('0')
|
||||
const ONE_HUNDRED_PERCENT = new Percent('1')
|
||||
// returns whether tradeB is better than tradeA by at least a threshold
|
||||
|
||||
// returns whether tradeB is better than tradeA by at least a threshold percentage amount
|
||||
export function isTradeBetter(
|
||||
tradeA: Trade | undefined,
|
||||
tradeB: Trade | undefined,
|
||||
@@ -150,8 +171,8 @@ export function isTradeBetter(
|
||||
|
||||
if (
|
||||
tradeA.tradeType !== tradeB.tradeType ||
|
||||
!tradeA.inputAmount.token.equals(tradeB.inputAmount.token) ||
|
||||
!tradeB.outputAmount.token.equals(tradeB.outputAmount.token)
|
||||
!currencyEquals(tradeA.inputAmount.currency, tradeB.inputAmount.currency) ||
|
||||
!currencyEquals(tradeB.outputAmount.currency, tradeB.outputAmount.currency)
|
||||
) {
|
||||
throw new Error('Trades are not comparable')
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { parseBytes32String } from '@ethersproject/strings'
|
||||
import { ChainId, Token, WETH } from '@uniswap/sdk'
|
||||
import { Currency, ETHER, Token } from '@uniswap/sdk'
|
||||
import { useMemo } from 'react'
|
||||
import { ALL_TOKENS } from '../constants/tokens'
|
||||
import { useDefaultTokenList } from '../state/lists/hooks'
|
||||
import { NEVER_RELOAD, useSingleCallResult } from '../state/multicall/hooks'
|
||||
import { useUserAddedTokens } from '../state/user/hooks'
|
||||
import { isAddress } from '../utils'
|
||||
@@ -12,30 +12,24 @@ import { useBytes32TokenContract, useTokenContract } from './useContract'
|
||||
export function useAllTokens(): { [address: string]: Token } {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const userAddedTokens = useUserAddedTokens()
|
||||
const allTokens = useDefaultTokenList()
|
||||
|
||||
return useMemo(() => {
|
||||
if (!chainId) return {}
|
||||
const tokens = userAddedTokens
|
||||
// reduce into all ALL_TOKENS filtered by the current chain
|
||||
.reduce<{ [address: string]: Token }>(
|
||||
(tokenMap, token) => {
|
||||
tokenMap[token.address] = token
|
||||
return tokenMap
|
||||
},
|
||||
// must make a copy because reduce modifies the map, and we do not
|
||||
// want to make a copy in every iteration
|
||||
{ ...ALL_TOKENS[chainId as ChainId] }
|
||||
)
|
||||
|
||||
const weth = WETH[chainId as ChainId]
|
||||
if (weth) {
|
||||
// we have to replace it as a workaround because if it is automatically
|
||||
// fetched by address it will cause an invariant when used in constructing
|
||||
// pairs since we replace the name and symbol with 'ETH' and 'Ether'
|
||||
tokens[weth.address] = weth
|
||||
}
|
||||
return tokens
|
||||
}, [userAddedTokens, chainId])
|
||||
return (
|
||||
userAddedTokens
|
||||
// reduce into all ALL_TOKENS filtered by the current chain
|
||||
.reduce<{ [address: string]: Token }>(
|
||||
(tokenMap, token) => {
|
||||
tokenMap[token.address] = token
|
||||
return tokenMap
|
||||
},
|
||||
// must make a copy because reduce modifies the map, and we do not
|
||||
// want to make a copy in every iteration
|
||||
{ ...allTokens[chainId] }
|
||||
)
|
||||
)
|
||||
}, [chainId, userAddedTokens, allTokens])
|
||||
}
|
||||
|
||||
// parse a name or symbol from a token response
|
||||
@@ -100,3 +94,9 @@ export function useToken(tokenAddress?: string): Token | undefined | null {
|
||||
tokenNameBytes32.result
|
||||
])
|
||||
}
|
||||
|
||||
export function useCurrency(currencyId: string | undefined): Currency | null | undefined {
|
||||
const isETH = currencyId?.toUpperCase() === 'ETH'
|
||||
const token = useToken(isETH ? undefined : currencyId)
|
||||
return isETH ? ETHER : token
|
||||
}
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import { Pair, Token, TokenAmount, Trade } from '@uniswap/sdk'
|
||||
import { Currency, CurrencyAmount, Pair, Token, Trade } from '@uniswap/sdk'
|
||||
import flatMap from 'lodash.flatmap'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { BASES_TO_CHECK_TRADES_AGAINST } from '../constants'
|
||||
import { usePairs } from '../data/Reserves'
|
||||
import { PairState, usePairs } from '../data/Reserves'
|
||||
import { wrappedCurrency } from '../utils/wrappedCurrency'
|
||||
|
||||
import { useActiveWeb3React } from './index'
|
||||
|
||||
function useAllCommonPairs(tokenA?: Token, tokenB?: Token): Pair[] {
|
||||
function useAllCommonPairs(currencyA?: Currency, currencyB?: Currency): Pair[] {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
|
||||
const bases: Token[] = chainId ? BASES_TO_CHECK_TRADES_AGAINST[chainId] : []
|
||||
|
||||
const [tokenA, tokenB] = chainId
|
||||
? [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)]
|
||||
: [undefined, undefined]
|
||||
|
||||
const allPairCombinations: [Token | undefined, Token | undefined][] = useMemo(
|
||||
() => [
|
||||
// the direct pair
|
||||
@@ -34,9 +39,9 @@ function useAllCommonPairs(tokenA?: Token, tokenB?: Token): Pair[] {
|
||||
Object.values(
|
||||
allPairs
|
||||
// filter out invalid pairs
|
||||
.filter((p): p is Pair => !!p)
|
||||
.filter((result): result is [PairState.EXISTS, Pair] => Boolean(result[0] === PairState.EXISTS && result[1]))
|
||||
// filter out duplicated pairs
|
||||
.reduce<{ [pairAddress: string]: Pair }>((memo, curr) => {
|
||||
.reduce<{ [pairAddress: string]: Pair }>((memo, [, curr]) => {
|
||||
memo[curr.liquidityToken.address] = memo[curr.liquidityToken.address] ?? curr
|
||||
return memo
|
||||
}, {})
|
||||
@@ -48,27 +53,32 @@ function useAllCommonPairs(tokenA?: Token, tokenB?: Token): Pair[] {
|
||||
/**
|
||||
* Returns the best trade for the exact amount of tokens in to the given token out
|
||||
*/
|
||||
export function useTradeExactIn(amountIn?: TokenAmount, tokenOut?: Token): Trade | null {
|
||||
const allowedPairs = useAllCommonPairs(amountIn?.token, tokenOut)
|
||||
export function useTradeExactIn(currencyAmountIn?: CurrencyAmount, currencyOut?: Currency): Trade | null {
|
||||
const allowedPairs = useAllCommonPairs(currencyAmountIn?.currency, currencyOut)
|
||||
|
||||
return useMemo(() => {
|
||||
if (amountIn && tokenOut && allowedPairs.length > 0) {
|
||||
return Trade.bestTradeExactIn(allowedPairs, amountIn, tokenOut, { maxHops: 3, maxNumResults: 1 })[0] ?? null
|
||||
if (currencyAmountIn && currencyOut && allowedPairs.length > 0) {
|
||||
return (
|
||||
Trade.bestTradeExactIn(allowedPairs, currencyAmountIn, currencyOut, { maxHops: 3, maxNumResults: 1 })[0] ?? null
|
||||
)
|
||||
}
|
||||
return null
|
||||
}, [allowedPairs, amountIn, tokenOut])
|
||||
}, [allowedPairs, currencyAmountIn, currencyOut])
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the best trade for the token in to the exact amount of token out
|
||||
*/
|
||||
export function useTradeExactOut(tokenIn?: Token, amountOut?: TokenAmount): Trade | null {
|
||||
const allowedPairs = useAllCommonPairs(tokenIn, amountOut?.token)
|
||||
export function useTradeExactOut(currencyIn?: Currency, currencyAmountOut?: CurrencyAmount): Trade | null {
|
||||
const allowedPairs = useAllCommonPairs(currencyIn, currencyAmountOut?.currency)
|
||||
|
||||
return useMemo(() => {
|
||||
if (tokenIn && amountOut && allowedPairs.length > 0) {
|
||||
return Trade.bestTradeExactOut(allowedPairs, tokenIn, amountOut, { maxHops: 3, maxNumResults: 1 })[0] ?? null
|
||||
if (currencyIn && currencyAmountOut && allowedPairs.length > 0) {
|
||||
return (
|
||||
Trade.bestTradeExactOut(allowedPairs, currencyIn, currencyAmountOut, { maxHops: 3, maxNumResults: 1 })[0] ??
|
||||
null
|
||||
)
|
||||
}
|
||||
return null
|
||||
}, [allowedPairs, tokenIn, amountOut])
|
||||
}, [allowedPairs, currencyIn, currencyAmountOut])
|
||||
}
|
||||
|
||||
@@ -72,21 +72,12 @@ export function useInactiveListener(suppress = false) {
|
||||
}
|
||||
}
|
||||
|
||||
const handleNetworkChanged = () => {
|
||||
// eat errors
|
||||
activate(injected, undefined, true).catch(error => {
|
||||
console.error('Failed to activate after networks changed', error)
|
||||
})
|
||||
}
|
||||
|
||||
ethereum.on('chainChanged', handleChainChanged)
|
||||
ethereum.on('networkChanged', handleNetworkChanged)
|
||||
ethereum.on('accountsChanged', handleAccountsChanged)
|
||||
|
||||
return () => {
|
||||
if (ethereum.removeListener) {
|
||||
ethereum.removeListener('chainChanged', handleChainChanged)
|
||||
ethereum.removeListener('networkChanged', handleNetworkChanged)
|
||||
ethereum.removeListener('accountsChanged', handleAccountsChanged)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MaxUint256 } from '@ethersproject/constants'
|
||||
import { TransactionResponse } from '@ethersproject/providers'
|
||||
import { Trade, WETH, TokenAmount } from '@uniswap/sdk'
|
||||
import { Trade, TokenAmount, CurrencyAmount, ETHER } from '@uniswap/sdk'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { ROUTER_ADDRESS } from '../constants'
|
||||
import { useTokenAllowance } from '../data/Allowances'
|
||||
@@ -22,27 +22,30 @@ export enum ApprovalState {
|
||||
|
||||
// returns a variable indicating the state of the approval and a function which approves if necessary or early returns
|
||||
export function useApproveCallback(
|
||||
amountToApprove?: TokenAmount,
|
||||
amountToApprove?: CurrencyAmount,
|
||||
spender?: string
|
||||
): [ApprovalState, () => Promise<void>] {
|
||||
const { account } = useActiveWeb3React()
|
||||
|
||||
const currentAllowance = useTokenAllowance(amountToApprove?.token, account ?? undefined, spender)
|
||||
const pendingApproval = useHasPendingApproval(amountToApprove?.token?.address, spender)
|
||||
const token = amountToApprove instanceof TokenAmount ? amountToApprove.token : undefined
|
||||
const currentAllowance = useTokenAllowance(token, account ?? undefined, spender)
|
||||
const pendingApproval = useHasPendingApproval(token?.address, spender)
|
||||
|
||||
// check the current approval status
|
||||
const approvalState: ApprovalState = useMemo(() => {
|
||||
if (!amountToApprove || !spender) return ApprovalState.UNKNOWN
|
||||
// we treat WETH as ETH which requires no approvals
|
||||
if (amountToApprove.token.equals(WETH[amountToApprove.token.chainId])) return ApprovalState.APPROVED
|
||||
if (amountToApprove.currency === ETHER) return ApprovalState.APPROVED
|
||||
// we might not have enough data to know whether or not we need to approve
|
||||
if (!currentAllowance) return ApprovalState.UNKNOWN
|
||||
if (pendingApproval) return ApprovalState.PENDING
|
||||
|
||||
// amountToApprove will be defined if currentAllowance is
|
||||
return currentAllowance.lessThan(amountToApprove) ? ApprovalState.NOT_APPROVED : ApprovalState.APPROVED
|
||||
return currentAllowance.lessThan(amountToApprove)
|
||||
? pendingApproval
|
||||
? ApprovalState.PENDING
|
||||
: ApprovalState.NOT_APPROVED
|
||||
: ApprovalState.APPROVED
|
||||
}, [amountToApprove, currentAllowance, pendingApproval, spender])
|
||||
|
||||
const tokenContract = useTokenContract(amountToApprove?.token?.address)
|
||||
const tokenContract = useTokenContract(token?.address)
|
||||
const addTransaction = useTransactionAdder()
|
||||
|
||||
const approve = useCallback(async (): Promise<void> => {
|
||||
@@ -50,6 +53,10 @@ export function useApproveCallback(
|
||||
console.error('approve was called unnecessarily')
|
||||
return
|
||||
}
|
||||
if (!token) {
|
||||
console.error('no token')
|
||||
return
|
||||
}
|
||||
|
||||
if (!tokenContract) {
|
||||
console.error('tokenContract is null')
|
||||
@@ -79,15 +86,15 @@ export function useApproveCallback(
|
||||
})
|
||||
.then((response: TransactionResponse) => {
|
||||
addTransaction(response, {
|
||||
summary: 'Approve ' + amountToApprove.token.symbol,
|
||||
approval: { tokenAddress: amountToApprove.token.address, spender: spender }
|
||||
summary: 'Approve ' + amountToApprove.currency.symbol,
|
||||
approval: { tokenAddress: token.address, spender: spender }
|
||||
})
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
console.debug('Failed to approve token', error)
|
||||
throw error
|
||||
})
|
||||
}, [approvalState, tokenContract, spender, amountToApprove, addTransaction])
|
||||
}, [approvalState, token, tokenContract, amountToApprove, spender, addTransaction])
|
||||
|
||||
return [approvalState, approve]
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { useCallback, useEffect } from 'react'
|
||||
|
||||
// modified from https://usehooks.com/useKeyPress/
|
||||
export default function useBodyKeyDown(targetKey: string, onKeyDown: () => void, suppressOnKeyDown = false) {
|
||||
const downHandler = useCallback(
|
||||
event => {
|
||||
const {
|
||||
target: { tagName },
|
||||
key
|
||||
} = event
|
||||
if (key === targetKey && tagName === 'BODY' && !suppressOnKeyDown) {
|
||||
event.preventDefault()
|
||||
onKeyDown()
|
||||
}
|
||||
},
|
||||
[targetKey, onKeyDown, suppressOnKeyDown]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('keydown', downHandler)
|
||||
return () => {
|
||||
window.removeEventListener('keydown', downHandler)
|
||||
}
|
||||
}, [downHandler])
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Contract } from '@ethersproject/contracts'
|
||||
import { ChainId } from '@uniswap/sdk'
|
||||
import { ChainId, WETH } from '@uniswap/sdk'
|
||||
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
|
||||
import { useMemo } from 'react'
|
||||
import { ERC20_BYTES32_ABI } from '../constants/abis/erc20'
|
||||
import UNISOCKS_ABI from '../constants/abis/unisocks.json'
|
||||
import ERC20_ABI from '../constants/abis/erc20.json'
|
||||
import WETH_ABI from '../constants/abis/weth.json'
|
||||
import { MIGRATOR_ABI, MIGRATOR_ADDRESS } from '../constants/abis/migrator'
|
||||
import { V1_EXCHANGE_ABI, V1_FACTORY_ABI, V1_FACTORY_ADDRESSES } from '../constants/v1'
|
||||
import { MULTICALL_ABI, MULTICALL_NETWORKS } from '../constants/multicall'
|
||||
import { V1_EXCHANGE_ABI, V1_FACTORY_ABI, V1_FACTORY_ADDRESSES } from '../constants/v1'
|
||||
import { getContract } from '../utils'
|
||||
import { useActiveWeb3React } from './index'
|
||||
|
||||
@@ -27,7 +29,7 @@ function useContract(address?: string, ABI?: any, withSignerIfPossible = true):
|
||||
|
||||
export function useV1FactoryContract(): Contract | null {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
return useContract(V1_FACTORY_ADDRESSES[chainId as ChainId], V1_FACTORY_ABI, false)
|
||||
return useContract(chainId && V1_FACTORY_ADDRESSES[chainId], V1_FACTORY_ABI, false)
|
||||
}
|
||||
|
||||
export function useV2MigratorContract(): Contract | null {
|
||||
@@ -42,6 +44,11 @@ export function useTokenContract(tokenAddress?: string, withSignerIfPossible?: b
|
||||
return useContract(tokenAddress, ERC20_ABI, withSignerIfPossible)
|
||||
}
|
||||
|
||||
export function useWETHContract(withSignerIfPossible?: boolean): Contract | null {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
return useContract(chainId ? WETH[chainId].address : undefined, WETH_ABI, withSignerIfPossible)
|
||||
}
|
||||
|
||||
export function useBytes32TokenContract(tokenAddress?: string, withSignerIfPossible?: boolean): Contract | null {
|
||||
return useContract(tokenAddress, ERC20_BYTES32_ABI, withSignerIfPossible)
|
||||
}
|
||||
@@ -52,5 +59,14 @@ export function usePairContract(pairAddress?: string, withSignerIfPossible?: boo
|
||||
|
||||
export function useMulticallContract(): Contract | null {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
return useContract(MULTICALL_NETWORKS[chainId as ChainId], MULTICALL_ABI, false)
|
||||
return useContract(chainId && MULTICALL_NETWORKS[chainId], MULTICALL_ABI, false)
|
||||
}
|
||||
|
||||
export function useSocksController(): Contract | null {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
return useContract(
|
||||
chainId === ChainId.MAINNET ? '0x65770b5283117639760beA3F867b69b3697a91dd' : undefined,
|
||||
UNISOCKS_ABI,
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
21
src/hooks/useENS.ts
Normal file
21
src/hooks/useENS.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { isAddress } from '../utils'
|
||||
import useENSAddress from './useENSAddress'
|
||||
import useENSName from './useENSName'
|
||||
|
||||
/**
|
||||
* Given a name or address, does a lookup to resolve to an address and name
|
||||
* @param nameOrAddress ENS name or address
|
||||
*/
|
||||
export default function useENS(
|
||||
nameOrAddress?: string | null
|
||||
): { loading: boolean; address: string | null; name: string | null } {
|
||||
const validated = isAddress(nameOrAddress)
|
||||
const reverseLookup = useENSName(validated ? validated : undefined)
|
||||
const lookup = useENSAddress(nameOrAddress)
|
||||
|
||||
return {
|
||||
loading: reverseLookup.loading || lookup.loading,
|
||||
address: validated ? validated : lookup.address,
|
||||
name: reverseLookup.ENSName ? reverseLookup.ENSName : !validated && lookup.address ? nameOrAddress || null : null
|
||||
}
|
||||
}
|
||||
46
src/hooks/useENSAddress.ts
Normal file
46
src/hooks/useENSAddress.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useActiveWeb3React } from './index'
|
||||
|
||||
/**
|
||||
* Does a lookup for an ENS name to find its address.
|
||||
*/
|
||||
export default function useENSAddress(ensName?: string | null): { loading: boolean; address: string | null } {
|
||||
const { library } = useActiveWeb3React()
|
||||
|
||||
const [address, setAddress] = useState<{ loading: boolean; address: string | null }>({
|
||||
loading: false,
|
||||
address: null
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!library || typeof ensName !== 'string') {
|
||||
setAddress({ loading: false, address: null })
|
||||
return
|
||||
} else {
|
||||
let stale = false
|
||||
setAddress({ loading: true, address: null })
|
||||
library
|
||||
.resolveName(ensName)
|
||||
.then(address => {
|
||||
if (!stale) {
|
||||
if (address) {
|
||||
setAddress({ loading: false, address })
|
||||
} else {
|
||||
setAddress({ loading: false, address: null })
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (!stale) {
|
||||
setAddress({ loading: false, address: null })
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
stale = true
|
||||
}
|
||||
}
|
||||
}, [library, ensName])
|
||||
|
||||
return address
|
||||
}
|
||||
@@ -6,39 +6,43 @@ import { useActiveWeb3React } from './index'
|
||||
* Does a reverse lookup for an address to find its ENS name.
|
||||
* Note this is not the same as looking up an ENS name to find an address.
|
||||
*/
|
||||
export default function useENSName(address?: string): string | null {
|
||||
export default function useENSName(address?: string): { ENSName: string | null; loading: boolean } {
|
||||
const { library } = useActiveWeb3React()
|
||||
|
||||
const [ENSName, setENSName] = useState<string | null>(null)
|
||||
const [ENSName, setENSName] = useState<{ ENSName: string | null; loading: boolean }>({
|
||||
loading: false,
|
||||
ENSName: null
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!library || !address) return
|
||||
const validated = isAddress(address)
|
||||
if (validated) {
|
||||
if (!library || !validated) {
|
||||
setENSName({ loading: false, ENSName: null })
|
||||
return
|
||||
} else {
|
||||
let stale = false
|
||||
setENSName({ loading: true, ENSName: null })
|
||||
library
|
||||
.lookupAddress(validated)
|
||||
.then(name => {
|
||||
if (!stale) {
|
||||
if (name) {
|
||||
setENSName(name)
|
||||
setENSName({ loading: false, ENSName: name })
|
||||
} else {
|
||||
setENSName(null)
|
||||
setENSName({ loading: false, ENSName: null })
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (!stale) {
|
||||
setENSName(null)
|
||||
setENSName({ loading: false, ENSName: null })
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
stale = true
|
||||
setENSName(null)
|
||||
}
|
||||
}
|
||||
return
|
||||
}, [library, address])
|
||||
|
||||
return ENSName
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
|
||||
export default function useInterval(callback: () => void, delay: null | number) {
|
||||
export default function useInterval(callback: () => void, delay: null | number, leading = true) {
|
||||
const savedCallback = useRef<() => void>()
|
||||
|
||||
// Remember the latest callback.
|
||||
@@ -16,10 +16,10 @@ export default function useInterval(callback: () => void, delay: null | number)
|
||||
}
|
||||
|
||||
if (delay !== null) {
|
||||
tick()
|
||||
if (leading) tick()
|
||||
const id = setInterval(tick, delay)
|
||||
return () => clearInterval(id)
|
||||
}
|
||||
return
|
||||
}, [delay])
|
||||
}, [delay, leading])
|
||||
}
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
const VISIBILITY_STATE_SUPPORTED = 'visibilityState' in document
|
||||
|
||||
function isWindowVisible() {
|
||||
return !VISIBILITY_STATE_SUPPORTED || document.visibilityState !== 'hidden'
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the window is currently visible to the user.
|
||||
*/
|
||||
export default function useIsWindowVisible(): boolean {
|
||||
const [focused, setFocused] = useState<boolean>(true)
|
||||
const [focused, setFocused] = useState<boolean>(isWindowVisible())
|
||||
const listener = useCallback(() => {
|
||||
setFocused(document.visibilityState !== 'hidden')
|
||||
setFocused(isWindowVisible())
|
||||
}, [setFocused])
|
||||
|
||||
useEffect(() => {
|
||||
if (!VISIBILITY_STATE_SUPPORTED) return
|
||||
|
||||
document.addEventListener('visibilitychange', listener)
|
||||
return () => {
|
||||
document.removeEventListener('visibilitychange', listener)
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { TransactionResponse } from '@ethersproject/providers'
|
||||
import { WETH, TokenAmount, JSBI, ChainId } from '@uniswap/sdk'
|
||||
import { useMemo } from 'react'
|
||||
import { useTransactionAdder } from '../state/transactions/hooks'
|
||||
import { useTokenBalanceTreatingWETHasETH } from '../state/wallet/hooks'
|
||||
|
||||
import { calculateGasMargin, getSigner, isAddress } from '../utils'
|
||||
import { useTokenContract } from './useContract'
|
||||
import { useActiveWeb3React } from './index'
|
||||
import useENSName from './useENSName'
|
||||
|
||||
// returns a callback for sending a token amount, treating WETH as ETH
|
||||
// returns null with invalid arguments
|
||||
export function useSendCallback(amount?: TokenAmount, recipient?: string): null | (() => Promise<string>) {
|
||||
const { library, account, chainId } = useActiveWeb3React()
|
||||
const addTransaction = useTransactionAdder()
|
||||
const ensName = useENSName(recipient)
|
||||
const tokenContract = useTokenContract(amount?.token?.address)
|
||||
const balance = useTokenBalanceTreatingWETHasETH(account ?? undefined, amount?.token)
|
||||
|
||||
return useMemo(() => {
|
||||
if (!amount) return null
|
||||
if (!amount.greaterThan(JSBI.BigInt(0))) return null
|
||||
if (!isAddress(recipient)) return null
|
||||
if (!balance) return null
|
||||
if (balance.lessThan(amount)) return null
|
||||
|
||||
const token = amount?.token
|
||||
|
||||
return async function onSend(): Promise<string> {
|
||||
if (!chainId || !library || !account || !tokenContract) {
|
||||
throw new Error('missing dependencies in onSend callback')
|
||||
}
|
||||
if (token.equals(WETH[chainId as ChainId])) {
|
||||
return getSigner(library, account)
|
||||
.sendTransaction({ to: recipient, value: BigNumber.from(amount.raw.toString()) })
|
||||
.then((response: TransactionResponse) => {
|
||||
addTransaction(response, {
|
||||
summary: 'Send ' + amount.toSignificant(3) + ' ' + token?.symbol + ' to ' + (ensName ?? recipient)
|
||||
})
|
||||
return response.hash
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
console.error('Failed to transfer ETH', error)
|
||||
throw error
|
||||
})
|
||||
} else {
|
||||
return tokenContract.estimateGas
|
||||
.transfer(recipient, amount.raw.toString())
|
||||
.then(estimatedGasLimit =>
|
||||
tokenContract
|
||||
.transfer(recipient, amount.raw.toString(), {
|
||||
gasLimit: calculateGasMargin(estimatedGasLimit)
|
||||
})
|
||||
.then((response: TransactionResponse) => {
|
||||
addTransaction(response, {
|
||||
summary: 'Send ' + amount.toSignificant(3) + ' ' + token.symbol + ' to ' + (ensName ?? recipient)
|
||||
})
|
||||
return response.hash
|
||||
})
|
||||
)
|
||||
.catch(error => {
|
||||
console.error('Failed token transfer', error)
|
||||
throw error
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [addTransaction, library, account, chainId, amount, ensName, recipient, tokenContract, balance])
|
||||
}
|
||||
19
src/hooks/useSocksBalance.ts
Normal file
19
src/hooks/useSocksBalance.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { JSBI } from '@uniswap/sdk'
|
||||
import { useMemo } from 'react'
|
||||
import { NEVER_RELOAD, useSingleCallResult } from '../state/multicall/hooks'
|
||||
import { useActiveWeb3React } from './index'
|
||||
import { useSocksController } from './useContract'
|
||||
|
||||
export default function useSocksBalance(): JSBI | undefined {
|
||||
const { account } = useActiveWeb3React()
|
||||
const socksContract = useSocksController()
|
||||
|
||||
const { result } = useSingleCallResult(socksContract, 'balanceOf', [account ?? undefined], NEVER_RELOAD)
|
||||
const data = result?.[0]
|
||||
return data ? JSBI.BigInt(data.toString()) : undefined
|
||||
}
|
||||
|
||||
export function useHasSocks(): boolean | undefined {
|
||||
const balance = useSocksBalance()
|
||||
return useMemo(() => balance && JSBI.greaterThan(balance, JSBI.BigInt(0)), [balance])
|
||||
}
|
||||
@@ -1,229 +1,86 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { MaxUint256 } from '@ethersproject/constants'
|
||||
import { Contract } from '@ethersproject/contracts'
|
||||
import { ChainId, Trade, TradeType, WETH } from '@uniswap/sdk'
|
||||
import { JSBI, Percent, Router, Trade, TradeType } from '@uniswap/sdk'
|
||||
import { useMemo } from 'react'
|
||||
import { DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE, ROUTER_ADDRESS } from '../constants'
|
||||
import { useTokenAllowance } from '../data/Allowances'
|
||||
import { BIPS_BASE, DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE } from '../constants'
|
||||
import { getTradeVersion, useV1TradeExchangeAddress } from '../data/V1'
|
||||
import { Field } from '../state/swap/actions'
|
||||
import { useTransactionAdder } from '../state/transactions/hooks'
|
||||
import { calculateGasMargin, getRouterContract, isAddress } from '../utils'
|
||||
import { computeSlippageAdjustedAmounts } from '../utils/prices'
|
||||
import { calculateGasMargin, getRouterContract, isAddress, shortenAddress } from '../utils'
|
||||
import v1SwapArguments from '../utils/v1SwapArguments'
|
||||
import { useActiveWeb3React } from './index'
|
||||
import { useV1ExchangeContract } from './useContract'
|
||||
import useENSName from './useENSName'
|
||||
import useENS from './useENS'
|
||||
import { Version } from './useToggledVersion'
|
||||
|
||||
enum SwapType {
|
||||
EXACT_TOKENS_FOR_TOKENS,
|
||||
EXACT_TOKENS_FOR_ETH,
|
||||
EXACT_ETH_FOR_TOKENS,
|
||||
TOKENS_FOR_EXACT_TOKENS,
|
||||
TOKENS_FOR_EXACT_ETH,
|
||||
ETH_FOR_EXACT_TOKENS,
|
||||
V1_EXACT_ETH_FOR_TOKENS,
|
||||
V1_EXACT_TOKENS_FOR_ETH,
|
||||
V1_EXACT_TOKENS_FOR_TOKENS,
|
||||
V1_ETH_FOR_EXACT_TOKENS,
|
||||
V1_TOKENS_FOR_EXACT_ETH,
|
||||
V1_TOKENS_FOR_EXACT_TOKENS
|
||||
}
|
||||
|
||||
function getSwapType(trade: Trade | undefined): SwapType | undefined {
|
||||
if (!trade) return undefined
|
||||
const chainId = trade.inputAmount.token.chainId
|
||||
const inputWETH = trade.inputAmount.token.equals(WETH[chainId])
|
||||
const outputWETH = trade.outputAmount.token.equals(WETH[chainId])
|
||||
const isExactIn = trade.tradeType === TradeType.EXACT_INPUT
|
||||
const isV1 = getTradeVersion(trade) === Version.v1
|
||||
if (isExactIn) {
|
||||
if (inputWETH) {
|
||||
return isV1 ? SwapType.V1_EXACT_ETH_FOR_TOKENS : SwapType.EXACT_ETH_FOR_TOKENS
|
||||
} else if (outputWETH) {
|
||||
return isV1 ? SwapType.V1_EXACT_TOKENS_FOR_ETH : SwapType.EXACT_TOKENS_FOR_ETH
|
||||
} else {
|
||||
return isV1 ? SwapType.V1_EXACT_TOKENS_FOR_TOKENS : SwapType.EXACT_TOKENS_FOR_TOKENS
|
||||
}
|
||||
} else {
|
||||
if (inputWETH) {
|
||||
return isV1 ? SwapType.V1_ETH_FOR_EXACT_TOKENS : SwapType.ETH_FOR_EXACT_TOKENS
|
||||
} else if (outputWETH) {
|
||||
return isV1 ? SwapType.V1_TOKENS_FOR_EXACT_ETH : SwapType.TOKENS_FOR_EXACT_ETH
|
||||
} else {
|
||||
return isV1 ? SwapType.V1_TOKENS_FOR_EXACT_TOKENS : SwapType.TOKENS_FOR_EXACT_TOKENS
|
||||
}
|
||||
}
|
||||
function isZero(hexNumber: string) {
|
||||
return /^0x0*$/.test(hexNumber)
|
||||
}
|
||||
|
||||
// returns a function that will execute a swap, if the parameters are all valid
|
||||
// and the user has approved the slippage adjusted input amount for the trade
|
||||
export function useSwapCallback(
|
||||
trade?: Trade, // trade to execute, required
|
||||
trade: Trade | undefined, // trade to execute, required
|
||||
allowedSlippage: number = INITIAL_ALLOWED_SLIPPAGE, // in bips
|
||||
deadline: number = DEFAULT_DEADLINE_FROM_NOW, // in seconds from now
|
||||
to?: string // recipient of output, optional
|
||||
recipientAddressOrName: string | null // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender
|
||||
): null | (() => Promise<string>) {
|
||||
const { account, chainId, library } = useActiveWeb3React()
|
||||
const addTransaction = useTransactionAdder()
|
||||
const recipient = to ? isAddress(to) : account
|
||||
const ensName = useENSName(to)
|
||||
|
||||
const { address: recipientAddress } = useENS(recipientAddressOrName)
|
||||
const recipient = recipientAddressOrName === null ? account : recipientAddress
|
||||
|
||||
const tradeVersion = getTradeVersion(trade)
|
||||
const v1Exchange = useV1ExchangeContract(useV1TradeExchangeAddress(trade), true)
|
||||
const inputAllowance = useTokenAllowance(
|
||||
trade?.inputAmount?.token,
|
||||
account ?? undefined,
|
||||
tradeVersion === Version.v1 ? v1Exchange?.address : ROUTER_ADDRESS
|
||||
)
|
||||
|
||||
return useMemo(() => {
|
||||
if (!trade || !recipient || !tradeVersion) return null
|
||||
|
||||
// will always be defined
|
||||
const {
|
||||
[Field.INPUT]: slippageAdjustedInput,
|
||||
[Field.OUTPUT]: slippageAdjustedOutput
|
||||
} = computeSlippageAdjustedAmounts(trade, allowedSlippage)
|
||||
|
||||
if (!slippageAdjustedInput || !slippageAdjustedOutput) return null
|
||||
|
||||
// no allowance
|
||||
if (
|
||||
!trade.inputAmount.token.equals(WETH[chainId as ChainId]) &&
|
||||
(!inputAllowance || slippageAdjustedInput.greaterThan(inputAllowance))
|
||||
) {
|
||||
return null
|
||||
}
|
||||
if (!trade || !recipient || !library || !account || !tradeVersion || !chainId) return null
|
||||
|
||||
return async function onSwap() {
|
||||
if (!chainId || !library || !account) {
|
||||
throw new Error('missing dependencies in onSwap callback')
|
||||
}
|
||||
|
||||
const contract: Contract | null =
|
||||
tradeVersion === Version.v2 ? getRouterContract(chainId, library, account) : v1Exchange
|
||||
if (!contract) {
|
||||
throw new Error('Failed to get a swap contract')
|
||||
}
|
||||
|
||||
const path = trade.route.path.map(t => t.address)
|
||||
const swapMethods = []
|
||||
|
||||
const deadlineFromNow: number = Math.ceil(Date.now() / 1000) + deadline
|
||||
switch (tradeVersion) {
|
||||
case Version.v2:
|
||||
swapMethods.push(
|
||||
Router.swapCallParameters(trade, {
|
||||
feeOnTransfer: false,
|
||||
allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE),
|
||||
recipient,
|
||||
ttl: deadline
|
||||
})
|
||||
)
|
||||
|
||||
const swapType = getSwapType(trade)
|
||||
|
||||
// let estimate: Function, method: Function,
|
||||
let methodNames: string[],
|
||||
args: Array<string | string[] | number>,
|
||||
value: BigNumber | null = null
|
||||
switch (swapType) {
|
||||
case SwapType.EXACT_TOKENS_FOR_TOKENS:
|
||||
methodNames = ['swapExactTokensForTokens', 'swapExactTokensForTokensSupportingFeeOnTransferTokens']
|
||||
args = [
|
||||
slippageAdjustedInput.raw.toString(),
|
||||
slippageAdjustedOutput.raw.toString(),
|
||||
path,
|
||||
recipient,
|
||||
deadlineFromNow
|
||||
]
|
||||
if (trade.tradeType === TradeType.EXACT_INPUT) {
|
||||
swapMethods.push(
|
||||
Router.swapCallParameters(trade, {
|
||||
feeOnTransfer: true,
|
||||
allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE),
|
||||
recipient,
|
||||
ttl: deadline
|
||||
})
|
||||
)
|
||||
}
|
||||
break
|
||||
case SwapType.TOKENS_FOR_EXACT_TOKENS:
|
||||
methodNames = ['swapTokensForExactTokens']
|
||||
args = [
|
||||
slippageAdjustedOutput.raw.toString(),
|
||||
slippageAdjustedInput.raw.toString(),
|
||||
path,
|
||||
recipient,
|
||||
deadlineFromNow
|
||||
]
|
||||
case Version.v1:
|
||||
swapMethods.push(
|
||||
v1SwapArguments(trade, {
|
||||
allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE),
|
||||
recipient,
|
||||
ttl: deadline
|
||||
})
|
||||
)
|
||||
break
|
||||
case SwapType.EXACT_ETH_FOR_TOKENS:
|
||||
methodNames = ['swapExactETHForTokens', 'swapExactETHForTokensSupportingFeeOnTransferTokens']
|
||||
args = [slippageAdjustedOutput.raw.toString(), path, recipient, deadlineFromNow]
|
||||
value = BigNumber.from(slippageAdjustedInput.raw.toString())
|
||||
break
|
||||
case SwapType.TOKENS_FOR_EXACT_ETH:
|
||||
methodNames = ['swapTokensForExactETH']
|
||||
args = [
|
||||
slippageAdjustedOutput.raw.toString(),
|
||||
slippageAdjustedInput.raw.toString(),
|
||||
path,
|
||||
recipient,
|
||||
deadlineFromNow
|
||||
]
|
||||
break
|
||||
case SwapType.EXACT_TOKENS_FOR_ETH:
|
||||
methodNames = ['swapExactTokensForETH', 'swapExactTokensForETHSupportingFeeOnTransferTokens']
|
||||
args = [
|
||||
slippageAdjustedInput.raw.toString(),
|
||||
slippageAdjustedOutput.raw.toString(),
|
||||
path,
|
||||
recipient,
|
||||
deadlineFromNow
|
||||
]
|
||||
break
|
||||
case SwapType.ETH_FOR_EXACT_TOKENS:
|
||||
methodNames = ['swapETHForExactTokens']
|
||||
args = [slippageAdjustedOutput.raw.toString(), path, recipient, deadlineFromNow]
|
||||
value = BigNumber.from(slippageAdjustedInput.raw.toString())
|
||||
break
|
||||
case SwapType.V1_EXACT_ETH_FOR_TOKENS:
|
||||
methodNames = ['ethToTokenTransferInput']
|
||||
args = [slippageAdjustedOutput.raw.toString(), deadlineFromNow, recipient]
|
||||
value = BigNumber.from(slippageAdjustedInput.raw.toString())
|
||||
break
|
||||
case SwapType.V1_EXACT_TOKENS_FOR_TOKENS:
|
||||
methodNames = ['tokenToTokenTransferInput']
|
||||
args = [
|
||||
slippageAdjustedInput.raw.toString(),
|
||||
slippageAdjustedOutput.raw.toString(),
|
||||
1,
|
||||
deadlineFromNow,
|
||||
recipient,
|
||||
trade.outputAmount.token.address
|
||||
]
|
||||
break
|
||||
case SwapType.V1_EXACT_TOKENS_FOR_ETH:
|
||||
methodNames = ['tokenToEthTransferOutput']
|
||||
args = [
|
||||
slippageAdjustedOutput.raw.toString(),
|
||||
slippageAdjustedInput.raw.toString(),
|
||||
deadlineFromNow,
|
||||
recipient
|
||||
]
|
||||
break
|
||||
case SwapType.V1_ETH_FOR_EXACT_TOKENS:
|
||||
methodNames = ['ethToTokenTransferOutput']
|
||||
args = [slippageAdjustedOutput.raw.toString(), deadlineFromNow, recipient]
|
||||
value = BigNumber.from(slippageAdjustedInput.raw.toString())
|
||||
break
|
||||
case SwapType.V1_TOKENS_FOR_EXACT_ETH:
|
||||
methodNames = ['tokenToEthTransferOutput']
|
||||
args = [
|
||||
slippageAdjustedOutput.raw.toString(),
|
||||
slippageAdjustedInput.raw.toString(),
|
||||
deadlineFromNow,
|
||||
recipient
|
||||
]
|
||||
break
|
||||
case SwapType.V1_TOKENS_FOR_EXACT_TOKENS:
|
||||
methodNames = ['tokenToTokenTransferOutput']
|
||||
args = [
|
||||
slippageAdjustedOutput.raw.toString(),
|
||||
slippageAdjustedInput.raw.toString(),
|
||||
MaxUint256.toString(),
|
||||
deadlineFromNow,
|
||||
recipient,
|
||||
trade.outputAmount.token.address
|
||||
]
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unhandled swap type: ${swapType}`)
|
||||
}
|
||||
|
||||
const safeGasEstimates: (BigNumber | undefined)[] = await Promise.all(
|
||||
methodNames.map(methodName =>
|
||||
contract.estimateGas[methodName](...args, value ? { value } : {})
|
||||
swapMethods.map(({ args, methodName, value }) =>
|
||||
contract.estimateGas[methodName](...args, value && !isZero(value) ? { value } : {})
|
||||
.then(calculateGasMargin)
|
||||
.catch(error => {
|
||||
console.error(`estimateGas failed for ${methodName}`, error)
|
||||
@@ -253,7 +110,7 @@ export function useSwapCallback(
|
||||
// if only 1 method exists, either:
|
||||
// a) the token is doing something weird not related to FoT (e.g. enforcing a whitelist)
|
||||
// b) the token is FoT and the user specified an exact output, which is not allowed
|
||||
if (methodNames.length === 1) {
|
||||
if (swapMethods.length === 1) {
|
||||
throw Error(
|
||||
`An error occurred. If either of the tokens you're swapping take a fee on transfer, you must specify an exact input amount.`
|
||||
)
|
||||
@@ -261,7 +118,7 @@ export function useSwapCallback(
|
||||
// if 2 methods exists, either:
|
||||
// a) the token is doing something weird not related to FoT (e.g. enforcing a whitelist)
|
||||
// b) the token is FoT and is taking more than the specified slippage
|
||||
else if (methodNames.length === 2) {
|
||||
else if (swapMethods.length === 2) {
|
||||
throw Error(
|
||||
`An error occurred. If either of the tokens you're swapping take a fee on transfer, you must specify a slippage tolerance higher than the fee.`
|
||||
)
|
||||
@@ -269,24 +126,31 @@ export function useSwapCallback(
|
||||
throw Error('This transaction would fail. Please contact support.')
|
||||
}
|
||||
} else {
|
||||
const methodName = methodNames[indexOfSuccessfulEstimation]
|
||||
const { methodName, args, value } = swapMethods[indexOfSuccessfulEstimation]
|
||||
const safeGasEstimate = safeGasEstimates[indexOfSuccessfulEstimation]
|
||||
|
||||
return contract[methodName](...args, {
|
||||
gasLimit: safeGasEstimate,
|
||||
...(value ? { value } : {})
|
||||
...(value && !isZero(value) ? { value } : {})
|
||||
})
|
||||
.then((response: any) => {
|
||||
const inputSymbol = trade.inputAmount.token.symbol
|
||||
const outputSymbol = trade.outputAmount.token.symbol
|
||||
const inputAmount = slippageAdjustedInput.toSignificant(3)
|
||||
const outputAmount = slippageAdjustedOutput.toSignificant(3)
|
||||
const inputSymbol = trade.inputAmount.currency.symbol
|
||||
const outputSymbol = trade.outputAmount.currency.symbol
|
||||
const inputAmount = trade.inputAmount.toSignificant(3)
|
||||
const outputAmount = trade.outputAmount.toSignificant(3)
|
||||
|
||||
const base = `Swap ${inputAmount} ${inputSymbol} for ${outputAmount} ${outputSymbol}`
|
||||
const withRecipient = recipient === account ? base : `${base} to ${ensName ?? recipient}`
|
||||
const withRecipient =
|
||||
recipient === account
|
||||
? base
|
||||
: `${base} to ${
|
||||
recipientAddressOrName && isAddress(recipientAddressOrName)
|
||||
? shortenAddress(recipientAddressOrName)
|
||||
: recipientAddressOrName
|
||||
}`
|
||||
|
||||
const withVersion =
|
||||
tradeVersion === Version.v2 ? withRecipient : `${withRecipient} on ${tradeVersion.toUpperCase()}`
|
||||
tradeVersion === Version.v2 ? withRecipient : `${withRecipient} on ${(tradeVersion as any).toUpperCase()}`
|
||||
|
||||
addTransaction(response, {
|
||||
summary: withVersion
|
||||
@@ -310,15 +174,14 @@ export function useSwapCallback(
|
||||
}, [
|
||||
trade,
|
||||
recipient,
|
||||
tradeVersion,
|
||||
allowedSlippage,
|
||||
chainId,
|
||||
inputAllowance,
|
||||
library,
|
||||
account,
|
||||
tradeVersion,
|
||||
chainId,
|
||||
allowedSlippage,
|
||||
v1Exchange,
|
||||
deadline,
|
||||
addTransaction,
|
||||
ensName
|
||||
recipientAddressOrName,
|
||||
addTransaction
|
||||
])
|
||||
}
|
||||
|
||||
75
src/hooks/useWrapCallback.ts
Normal file
75
src/hooks/useWrapCallback.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { Currency, currencyEquals, ETHER, WETH } from '@uniswap/sdk'
|
||||
import { useMemo } from 'react'
|
||||
import { tryParseAmount } from '../state/swap/hooks'
|
||||
import { useTransactionAdder } from '../state/transactions/hooks'
|
||||
import { useCurrencyBalance } from '../state/wallet/hooks'
|
||||
import { useActiveWeb3React } from './index'
|
||||
import { useWETHContract } from './useContract'
|
||||
|
||||
export enum WrapType {
|
||||
NOT_APPLICABLE,
|
||||
WRAP,
|
||||
UNWRAP
|
||||
}
|
||||
|
||||
const NOT_APPLICABLE = { wrapType: WrapType.NOT_APPLICABLE }
|
||||
/**
|
||||
* Given the selected input and output currency, return a wrap callback
|
||||
* @param inputCurrency the selected input currency
|
||||
* @param outputCurrency the selected output currency
|
||||
* @param typedValue the user input value
|
||||
*/
|
||||
export default function useWrapCallback(
|
||||
inputCurrency: Currency | undefined,
|
||||
outputCurrency: Currency | undefined,
|
||||
typedValue: string | undefined
|
||||
): { wrapType: WrapType; execute?: undefined | (() => Promise<void>); error?: string } {
|
||||
const { chainId, account } = useActiveWeb3React()
|
||||
const wethContract = useWETHContract()
|
||||
const balance = useCurrencyBalance(account ?? undefined, inputCurrency)
|
||||
// we can always parse the amount typed as the input currency, since wrapping is 1:1
|
||||
const inputAmount = useMemo(() => tryParseAmount(typedValue, inputCurrency), [inputCurrency, typedValue])
|
||||
const addTransaction = useTransactionAdder()
|
||||
|
||||
return useMemo(() => {
|
||||
if (!wethContract || !chainId || !inputCurrency || !outputCurrency) return NOT_APPLICABLE
|
||||
|
||||
const sufficientBalance = inputAmount && balance && !balance.lessThan(inputAmount)
|
||||
|
||||
if (inputCurrency === ETHER && currencyEquals(WETH[chainId], outputCurrency)) {
|
||||
return {
|
||||
wrapType: WrapType.WRAP,
|
||||
execute:
|
||||
sufficientBalance && inputAmount
|
||||
? async () => {
|
||||
try {
|
||||
const txReceipt = await wethContract.deposit({ value: `0x${inputAmount.raw.toString(16)}` })
|
||||
addTransaction(txReceipt, { summary: `Wrap ${inputAmount.toSignificant(6)} ETH to WETH` })
|
||||
} catch (error) {
|
||||
console.error('Could not deposit', error)
|
||||
}
|
||||
}
|
||||
: undefined,
|
||||
error: sufficientBalance ? undefined : 'Insufficient ETH balance'
|
||||
}
|
||||
} else if (currencyEquals(WETH[chainId], inputCurrency) && outputCurrency === ETHER) {
|
||||
return {
|
||||
wrapType: WrapType.UNWRAP,
|
||||
execute:
|
||||
sufficientBalance && inputAmount
|
||||
? async () => {
|
||||
try {
|
||||
const txReceipt = await wethContract.withdraw(`0x${inputAmount.raw.toString(16)}`)
|
||||
addTransaction(txReceipt, { summary: `Unwrap ${inputAmount.toSignificant(6)} WETH to ETH` })
|
||||
} catch (error) {
|
||||
console.error('Could not withdraw', error)
|
||||
}
|
||||
}
|
||||
: undefined,
|
||||
error: sufficientBalance ? undefined : 'Insufficient WETH balance'
|
||||
}
|
||||
} else {
|
||||
return NOT_APPLICABLE
|
||||
}
|
||||
}, [wethContract, chainId, inputCurrency, outputCurrency, inputAmount, balance, addTransaction])
|
||||
}
|
||||
@@ -3,15 +3,13 @@ import { initReactI18next } from 'react-i18next'
|
||||
import XHR from 'i18next-xhr-backend'
|
||||
import LanguageDetector from 'i18next-browser-languagedetector'
|
||||
|
||||
const LOAD_PATH: string = process.env.PUBLIC_URL === '.' ? `./locales/{{lng}}.json` : '/locales/{{lng}}.json'
|
||||
|
||||
i18next
|
||||
.use(XHR)
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
backend: {
|
||||
loadPath: LOAD_PATH
|
||||
loadPath: `./locales/{{lng}}.json`
|
||||
},
|
||||
react: {
|
||||
useSuspense: true
|
||||
|
||||
@@ -6,17 +6,23 @@ import ReactDOM from 'react-dom'
|
||||
import ReactGA from 'react-ga'
|
||||
import { Provider } from 'react-redux'
|
||||
import { NetworkContextName } from './constants'
|
||||
import 'inter-ui'
|
||||
import './i18n'
|
||||
import App from './pages/App'
|
||||
import store from './state'
|
||||
import ApplicationUpdater from './state/application/updater'
|
||||
import TransactionUpdater from './state/transactions/updater'
|
||||
import ListsUpdater from './state/lists/updater'
|
||||
import UserUpdater from './state/user/updater'
|
||||
import MulticallUpdater from './state/multicall/updater'
|
||||
import ThemeProvider, { FixedGlobalStyle, ThemedGlobalStyle } from './theme'
|
||||
|
||||
const Web3ProviderNetwork = createWeb3ReactRoot(NetworkContextName)
|
||||
|
||||
if ('ethereum' in window) {
|
||||
;(window.ethereum as any).autoRefreshOnNetworkChange = false
|
||||
}
|
||||
|
||||
function getLibrary(provider: any): Web3Provider {
|
||||
const library = new Web3Provider(provider)
|
||||
library.pollingInterval = 15000
|
||||
@@ -33,9 +39,17 @@ if (typeof GOOGLE_ANALYTICS_ID === 'string') {
|
||||
ReactGA.initialize('test', { testMode: true, debug: true })
|
||||
}
|
||||
|
||||
window.addEventListener('error', error => {
|
||||
ReactGA.exception({
|
||||
description: `${error.message} @ ${error.filename}:${error.lineno}:${error.colno}`,
|
||||
fatal: true
|
||||
})
|
||||
})
|
||||
|
||||
function Updaters() {
|
||||
return (
|
||||
<>
|
||||
<ListsUpdater />
|
||||
<UserUpdater />
|
||||
<ApplicationUpdater />
|
||||
<TransactionUpdater />
|
||||
|
||||
67
src/pages/AddLiquidity/ConfirmAddModalBottom.tsx
Normal file
67
src/pages/AddLiquidity/ConfirmAddModalBottom.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { Currency, CurrencyAmount, Fraction, Percent } from '@uniswap/sdk'
|
||||
import React from 'react'
|
||||
import { Text } from 'rebass'
|
||||
import { ButtonPrimary } from '../../components/Button'
|
||||
import { RowBetween, RowFixed } from '../../components/Row'
|
||||
import CurrencyLogo from '../../components/CurrencyLogo'
|
||||
import { Field } from '../../state/mint/actions'
|
||||
import { TYPE } from '../../theme'
|
||||
|
||||
export function ConfirmAddModalBottom({
|
||||
noLiquidity,
|
||||
price,
|
||||
currencies,
|
||||
parsedAmounts,
|
||||
poolTokenPercentage,
|
||||
onAdd
|
||||
}: {
|
||||
noLiquidity?: boolean
|
||||
price?: Fraction
|
||||
currencies: { [field in Field]?: Currency }
|
||||
parsedAmounts: { [field in Field]?: CurrencyAmount }
|
||||
poolTokenPercentage?: Percent
|
||||
onAdd: () => void
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<RowBetween>
|
||||
<TYPE.body>{currencies[Field.CURRENCY_A]?.symbol} Deposited</TYPE.body>
|
||||
<RowFixed>
|
||||
<CurrencyLogo currency={currencies[Field.CURRENCY_A]} style={{ marginRight: '8px' }} />
|
||||
<TYPE.body>{parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)}</TYPE.body>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<TYPE.body>{currencies[Field.CURRENCY_B]?.symbol} Deposited</TYPE.body>
|
||||
<RowFixed>
|
||||
<CurrencyLogo currency={currencies[Field.CURRENCY_B]} style={{ marginRight: '8px' }} />
|
||||
<TYPE.body>{parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)}</TYPE.body>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<TYPE.body>Rates</TYPE.body>
|
||||
<TYPE.body>
|
||||
{`1 ${currencies[Field.CURRENCY_A]?.symbol} = ${price?.toSignificant(4)} ${
|
||||
currencies[Field.CURRENCY_B]?.symbol
|
||||
}`}
|
||||
</TYPE.body>
|
||||
</RowBetween>
|
||||
<RowBetween style={{ justifyContent: 'flex-end' }}>
|
||||
<TYPE.body>
|
||||
{`1 ${currencies[Field.CURRENCY_B]?.symbol} = ${price?.invert().toSignificant(4)} ${
|
||||
currencies[Field.CURRENCY_A]?.symbol
|
||||
}`}
|
||||
</TYPE.body>
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<TYPE.body>Share of Pool:</TYPE.body>
|
||||
<TYPE.body>{noLiquidity ? '100' : poolTokenPercentage?.toSignificant(4)}%</TYPE.body>
|
||||
</RowBetween>
|
||||
<ButtonPrimary style={{ margin: '20px 0 0 0' }} onClick={onAdd}>
|
||||
<Text fontWeight={500} fontSize={20}>
|
||||
{noLiquidity ? 'Create Pool & Supply' : 'Confirm Supply'}
|
||||
</Text>
|
||||
</ButtonPrimary>
|
||||
</>
|
||||
)
|
||||
}
|
||||
52
src/pages/AddLiquidity/PoolPriceBar.tsx
Normal file
52
src/pages/AddLiquidity/PoolPriceBar.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Currency, Fraction, Percent } from '@uniswap/sdk'
|
||||
import React, { useContext } from 'react'
|
||||
import { Text } from 'rebass'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { AutoColumn } from '../../components/Column'
|
||||
import { AutoRow } from '../../components/Row'
|
||||
import { ONE_BIPS } from '../../constants'
|
||||
import { Field } from '../../state/mint/actions'
|
||||
import { TYPE } from '../../theme'
|
||||
|
||||
export const PoolPriceBar = ({
|
||||
currencies,
|
||||
noLiquidity,
|
||||
poolTokenPercentage,
|
||||
price
|
||||
}: {
|
||||
currencies: { [field in Field]?: Currency }
|
||||
noLiquidity?: boolean
|
||||
poolTokenPercentage?: Percent
|
||||
price?: Fraction
|
||||
}) => {
|
||||
const theme = useContext(ThemeContext)
|
||||
return (
|
||||
<AutoColumn gap="md">
|
||||
<AutoRow justify="space-around" gap="4px">
|
||||
<AutoColumn justify="center">
|
||||
<TYPE.black>{price?.toSignificant(6) ?? '0'}</TYPE.black>
|
||||
<Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}>
|
||||
{currencies[Field.CURRENCY_B]?.symbol} per {currencies[Field.CURRENCY_A]?.symbol}
|
||||
</Text>
|
||||
</AutoColumn>
|
||||
<AutoColumn justify="center">
|
||||
<TYPE.black>{price?.invert().toSignificant(6) ?? '0'}</TYPE.black>
|
||||
<Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}>
|
||||
{currencies[Field.CURRENCY_A]?.symbol} per {currencies[Field.CURRENCY_B]?.symbol}
|
||||
</Text>
|
||||
</AutoColumn>
|
||||
<AutoColumn justify="center">
|
||||
<TYPE.black>
|
||||
{noLiquidity && price
|
||||
? '100'
|
||||
: (poolTokenPercentage?.lessThan(ONE_BIPS) ? '<0.01' : poolTokenPercentage?.toFixed(2)) ?? '0'}
|
||||
%
|
||||
</TYPE.black>
|
||||
<Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}>
|
||||
Share of Pool
|
||||
</Text>
|
||||
</AutoColumn>
|
||||
</AutoRow>
|
||||
</AutoColumn>
|
||||
)
|
||||
}
|
||||
@@ -1,49 +1,62 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { TokenAmount, WETH } from '@uniswap/sdk'
|
||||
import React, { useContext, useState } from 'react'
|
||||
import { TransactionResponse } from '@ethersproject/providers'
|
||||
import { Currency, currencyEquals, ETHER, TokenAmount, WETH } from '@uniswap/sdk'
|
||||
import React, { useCallback, useContext, useState } from 'react'
|
||||
import { Plus } from 'react-feather'
|
||||
import ReactGA from 'react-ga'
|
||||
import { RouteComponentProps } from 'react-router-dom'
|
||||
import { Text } from 'rebass'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { ButtonLight, ButtonPrimary, ButtonError } from '../../components/Button'
|
||||
import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button'
|
||||
import { BlueCard, GreyCard, LightCard } from '../../components/Card'
|
||||
import { AutoColumn, ColumnCenter } from '../../components/Column'
|
||||
import ConfirmationModal from '../../components/ConfirmationModal'
|
||||
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
|
||||
import DoubleLogo from '../../components/DoubleLogo'
|
||||
import PositionCard from '../../components/PositionCard'
|
||||
import Row, { AutoRow, RowBetween, RowFixed, RowFlat } from '../../components/Row'
|
||||
import DoubleCurrencyLogo from '../../components/DoubleLogo'
|
||||
import { AddRemoveTabs } from '../../components/NavigationTabs'
|
||||
import { MinimalPositionCard } from '../../components/PositionCard'
|
||||
import Row, { RowBetween, RowFlat } from '../../components/Row'
|
||||
|
||||
import TokenLogo from '../../components/TokenLogo'
|
||||
|
||||
import { ROUTER_ADDRESS, MIN_ETH, ONE_BIPS } from '../../constants'
|
||||
import { ROUTER_ADDRESS } from '../../constants'
|
||||
import { PairState } from '../../data/Reserves'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useCurrency } from '../../hooks/Tokens'
|
||||
import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
|
||||
import { useWalletModalToggle } from '../../state/application/hooks'
|
||||
import { Field } from '../../state/mint/actions'
|
||||
import { useDerivedMintInfo, useMintActionHandlers, useMintState } from '../../state/mint/hooks'
|
||||
|
||||
import { useTransactionAdder } from '../../state/transactions/hooks'
|
||||
import { useIsExpertMode, useUserDeadline, useUserSlippageTolerance } from '../../state/user/hooks'
|
||||
import { TYPE } from '../../theme'
|
||||
import { calculateGasMargin, calculateSlippageAmount, getRouterContract } from '../../utils'
|
||||
import { maxAmountSpend } from '../../utils/maxAmountSpend'
|
||||
import { wrappedCurrency } from '../../utils/wrappedCurrency'
|
||||
import AppBody from '../AppBody'
|
||||
import { Dots, Wrapper } from '../Pool/styleds'
|
||||
import {
|
||||
useDefaultsFromURLMatchParams,
|
||||
useMintState,
|
||||
useDerivedMintInfo,
|
||||
useMintActionHandlers
|
||||
} from '../../state/mint/hooks'
|
||||
import { Field } from '../../state/mint/actions'
|
||||
import { useApproveCallback, ApprovalState } from '../../hooks/useApproveCallback'
|
||||
import { useWalletModalToggle } from '../../state/application/hooks'
|
||||
import { useUserSlippageTolerance, useUserDeadline, useIsExpertMode } from '../../state/user/hooks'
|
||||
|
||||
export default function AddLiquidity({ match: { params } }: RouteComponentProps<{ tokens: string }>) {
|
||||
useDefaultsFromURLMatchParams(params)
|
||||
import { ConfirmAddModalBottom } from './ConfirmAddModalBottom'
|
||||
import { currencyId } from '../../utils/currencyId'
|
||||
import { PoolPriceBar } from './PoolPriceBar'
|
||||
|
||||
export default function AddLiquidity({
|
||||
match: {
|
||||
params: { currencyIdA, currencyIdB }
|
||||
},
|
||||
history
|
||||
}: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string }>) {
|
||||
const { account, chainId, library } = useActiveWeb3React()
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
// toggle wallet when disconnected
|
||||
const toggleWalletModal = useWalletModalToggle()
|
||||
const currencyA = useCurrency(currencyIdA)
|
||||
const currencyB = useCurrency(currencyIdB)
|
||||
|
||||
const oneCurrencyIsWETH = Boolean(
|
||||
chainId &&
|
||||
((currencyA && currencyEquals(currencyA, WETH[chainId])) ||
|
||||
(currencyB && currencyEquals(currencyB, WETH[chainId])))
|
||||
)
|
||||
|
||||
const toggleWalletModal = useWalletModalToggle() // toggle wallet when disconnected
|
||||
|
||||
const expertMode = useIsExpertMode()
|
||||
|
||||
@@ -51,17 +64,18 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
|
||||
const { independentField, typedValue, otherTypedValue } = useMintState()
|
||||
const {
|
||||
dependentField,
|
||||
tokens,
|
||||
currencies,
|
||||
pair,
|
||||
tokenBalances,
|
||||
pairState,
|
||||
currencyBalances,
|
||||
parsedAmounts,
|
||||
price,
|
||||
noLiquidity,
|
||||
liquidityMinted,
|
||||
poolTokenPercentage,
|
||||
error
|
||||
} = useDerivedMintInfo()
|
||||
const { onUserInput } = useMintActionHandlers()
|
||||
} = useDerivedMintInfo(currencyA ?? undefined, currencyB ?? undefined)
|
||||
const { onFieldAInput, onFieldBInput } = useMintActionHandlers(noLiquidity)
|
||||
|
||||
const isValid = !error
|
||||
|
||||
@@ -81,72 +95,75 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
|
||||
}
|
||||
|
||||
// get the max amounts user can add
|
||||
const maxAmounts: { [field in Field]?: TokenAmount } = [Field.TOKEN_A, Field.TOKEN_B].reduce((accumulator, field) => {
|
||||
return {
|
||||
...accumulator,
|
||||
[field]:
|
||||
!!tokenBalances[field] &&
|
||||
!!tokens[field] &&
|
||||
!!WETH[chainId] &&
|
||||
tokenBalances[field].greaterThan(
|
||||
new TokenAmount(tokens[field], tokens[field].equals(WETH[chainId]) ? MIN_ETH : '0')
|
||||
)
|
||||
? tokens[field].equals(WETH[chainId])
|
||||
? tokenBalances[field].subtract(new TokenAmount(WETH[chainId], MIN_ETH))
|
||||
: tokenBalances[field]
|
||||
: undefined
|
||||
}
|
||||
}, {})
|
||||
|
||||
const atMaxAmounts: { [field in Field]?: TokenAmount } = [Field.TOKEN_A, Field.TOKEN_B].reduce(
|
||||
const maxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
|
||||
(accumulator, field) => {
|
||||
return {
|
||||
...accumulator,
|
||||
[field]: maxAmounts[field] && parsedAmounts[field] ? maxAmounts[field].equalTo(parsedAmounts[field]) : undefined
|
||||
[field]: maxAmountSpend(currencyBalances[field])
|
||||
}
|
||||
},
|
||||
{}
|
||||
)
|
||||
|
||||
const atMaxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
|
||||
(accumulator, field) => {
|
||||
return {
|
||||
...accumulator,
|
||||
[field]: maxAmounts[field]?.equalTo(parsedAmounts[field] ?? '0')
|
||||
}
|
||||
},
|
||||
{}
|
||||
)
|
||||
|
||||
// check whether the user has approved the router on the tokens
|
||||
const [approvalA, approveACallback] = useApproveCallback(parsedAmounts[Field.TOKEN_A], ROUTER_ADDRESS)
|
||||
const [approvalB, approveBCallback] = useApproveCallback(parsedAmounts[Field.TOKEN_B], ROUTER_ADDRESS)
|
||||
const [approvalA, approveACallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_A], ROUTER_ADDRESS)
|
||||
const [approvalB, approveBCallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_B], ROUTER_ADDRESS)
|
||||
|
||||
const addTransaction = useTransactionAdder()
|
||||
|
||||
async function onAdd() {
|
||||
if (!chainId || !library || !account) return
|
||||
const router = getRouterContract(chainId, library, account)
|
||||
|
||||
const { [Field.CURRENCY_A]: parsedAmountA, [Field.CURRENCY_B]: parsedAmountB } = parsedAmounts
|
||||
if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB) {
|
||||
return
|
||||
}
|
||||
|
||||
const amountsMin = {
|
||||
[Field.TOKEN_A]: calculateSlippageAmount(parsedAmounts[Field.TOKEN_A], noLiquidity ? 0 : allowedSlippage)[0],
|
||||
[Field.TOKEN_B]: calculateSlippageAmount(parsedAmounts[Field.TOKEN_B], noLiquidity ? 0 : allowedSlippage)[0]
|
||||
[Field.CURRENCY_A]: calculateSlippageAmount(parsedAmountA, noLiquidity ? 0 : allowedSlippage)[0],
|
||||
[Field.CURRENCY_B]: calculateSlippageAmount(parsedAmountB, noLiquidity ? 0 : allowedSlippage)[0]
|
||||
}
|
||||
|
||||
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
|
||||
|
||||
let estimate, method: Function, args: Array<string | string[] | number>, value: BigNumber | null
|
||||
if (tokens[Field.TOKEN_A].equals(WETH[chainId]) || tokens[Field.TOKEN_B].equals(WETH[chainId])) {
|
||||
const tokenBIsETH = tokens[Field.TOKEN_B].equals(WETH[chainId])
|
||||
let estimate,
|
||||
method: (...args: any) => Promise<TransactionResponse>,
|
||||
args: Array<string | string[] | number>,
|
||||
value: BigNumber | null
|
||||
if (currencyA === ETHER || currencyB === ETHER) {
|
||||
const tokenBIsETH = currencyB === ETHER
|
||||
estimate = router.estimateGas.addLiquidityETH
|
||||
method = router.addLiquidityETH
|
||||
args = [
|
||||
tokens[tokenBIsETH ? Field.TOKEN_A : Field.TOKEN_B].address, // token
|
||||
parsedAmounts[tokenBIsETH ? Field.TOKEN_A : Field.TOKEN_B].raw.toString(), // token desired
|
||||
amountsMin[tokenBIsETH ? Field.TOKEN_A : Field.TOKEN_B].toString(), // token min
|
||||
amountsMin[tokenBIsETH ? Field.TOKEN_B : Field.TOKEN_A].toString(), // eth min
|
||||
wrappedCurrency(tokenBIsETH ? currencyA : currencyB, chainId)?.address ?? '', // token
|
||||
(tokenBIsETH ? parsedAmountA : parsedAmountB).raw.toString(), // token desired
|
||||
amountsMin[tokenBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(), // token min
|
||||
amountsMin[tokenBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(), // eth min
|
||||
account,
|
||||
deadlineFromNow
|
||||
]
|
||||
value = BigNumber.from(parsedAmounts[tokenBIsETH ? Field.TOKEN_B : Field.TOKEN_A].raw.toString())
|
||||
value = BigNumber.from((tokenBIsETH ? parsedAmountB : parsedAmountA).raw.toString())
|
||||
} else {
|
||||
estimate = router.estimateGas.addLiquidity
|
||||
method = router.addLiquidity
|
||||
args = [
|
||||
tokens[Field.TOKEN_A].address,
|
||||
tokens[Field.TOKEN_B].address,
|
||||
parsedAmounts[Field.TOKEN_A].raw.toString(),
|
||||
parsedAmounts[Field.TOKEN_B].raw.toString(),
|
||||
amountsMin[Field.TOKEN_A].toString(),
|
||||
amountsMin[Field.TOKEN_B].toString(),
|
||||
wrappedCurrency(currencyA, chainId)?.address ?? '',
|
||||
wrappedCurrency(currencyB, chainId)?.address ?? '',
|
||||
parsedAmountA.raw.toString(),
|
||||
parsedAmountB.raw.toString(),
|
||||
amountsMin[Field.CURRENCY_A].toString(),
|
||||
amountsMin[Field.CURRENCY_B].toString(),
|
||||
account,
|
||||
deadlineFromNow
|
||||
]
|
||||
@@ -165,13 +182,13 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
|
||||
addTransaction(response, {
|
||||
summary:
|
||||
'Add ' +
|
||||
parsedAmounts[Field.TOKEN_A]?.toSignificant(3) +
|
||||
parsedAmounts[Field.CURRENCY_A]?.toSignificant(3) +
|
||||
' ' +
|
||||
tokens[Field.TOKEN_A]?.symbol +
|
||||
currencies[Field.CURRENCY_A]?.symbol +
|
||||
' and ' +
|
||||
parsedAmounts[Field.TOKEN_B]?.toSignificant(3) +
|
||||
parsedAmounts[Field.CURRENCY_B]?.toSignificant(3) +
|
||||
' ' +
|
||||
tokens[Field.TOKEN_B]?.symbol
|
||||
currencies[Field.CURRENCY_B]?.symbol
|
||||
})
|
||||
|
||||
setTxHash(response.hash)
|
||||
@@ -179,7 +196,7 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
|
||||
ReactGA.event({
|
||||
category: 'Liquidity',
|
||||
action: 'Add',
|
||||
label: [tokens[Field.TOKEN_A]?.symbol, tokens[Field.TOKEN_B]?.symbol].join('/')
|
||||
label: [currencies[Field.CURRENCY_A]?.symbol, currencies[Field.CURRENCY_B]?.symbol].join('/')
|
||||
})
|
||||
})
|
||||
)
|
||||
@@ -198,9 +215,13 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
|
||||
<LightCard mt="20px" borderRadius="20px">
|
||||
<RowFlat>
|
||||
<Text fontSize="48px" fontWeight={500} lineHeight="42px" marginRight={10}>
|
||||
{tokens[Field.TOKEN_A]?.symbol + '/' + tokens[Field.TOKEN_B]?.symbol}
|
||||
{currencies[Field.CURRENCY_A]?.symbol + '/' + currencies[Field.CURRENCY_B]?.symbol}
|
||||
</Text>
|
||||
<DoubleLogo a0={tokens[Field.TOKEN_A]?.address} a1={tokens[Field.TOKEN_B]?.address} size={30} />
|
||||
<DoubleCurrencyLogo
|
||||
currency0={currencies[Field.CURRENCY_A]}
|
||||
currency1={currencies[Field.CURRENCY_B]}
|
||||
size={30}
|
||||
/>
|
||||
</RowFlat>
|
||||
</LightCard>
|
||||
</AutoColumn>
|
||||
@@ -210,11 +231,15 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
|
||||
<Text fontSize="48px" fontWeight={500} lineHeight="42px" marginRight={10}>
|
||||
{liquidityMinted?.toSignificant(6)}
|
||||
</Text>
|
||||
<DoubleLogo a0={tokens[Field.TOKEN_A]?.address} a1={tokens[Field.TOKEN_B]?.address} size={30} />
|
||||
<DoubleCurrencyLogo
|
||||
currency0={currencies[Field.CURRENCY_A]}
|
||||
currency1={currencies[Field.CURRENCY_B]}
|
||||
size={30}
|
||||
/>
|
||||
</RowFlat>
|
||||
<Row>
|
||||
<Text fontSize="24px">
|
||||
{tokens[Field.TOKEN_A]?.symbol + '/' + tokens[Field.TOKEN_B]?.symbol + ' Pool Tokens'}
|
||||
{currencies[Field.CURRENCY_A]?.symbol + '/' + currencies[Field.CURRENCY_B]?.symbol + ' Pool Tokens'}
|
||||
</Text>
|
||||
</Row>
|
||||
<TYPE.italic fontSize={12} textAlign="left" padding={'8px 0 0 0 '}>
|
||||
@@ -227,86 +252,52 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
|
||||
|
||||
const modalBottom = () => {
|
||||
return (
|
||||
<>
|
||||
<RowBetween>
|
||||
<TYPE.body>{tokens[Field.TOKEN_A]?.symbol} Deposited</TYPE.body>
|
||||
<RowFixed>
|
||||
<TokenLogo address={tokens[Field.TOKEN_A]?.address} style={{ marginRight: '8px' }} />
|
||||
<TYPE.body>{parsedAmounts[Field.TOKEN_A]?.toSignificant(6)}</TYPE.body>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<TYPE.body>{tokens[Field.TOKEN_B]?.symbol} Deposited</TYPE.body>
|
||||
<RowFixed>
|
||||
<TokenLogo address={tokens[Field.TOKEN_B]?.address} style={{ marginRight: '8px' }} />
|
||||
<TYPE.body>{parsedAmounts[Field.TOKEN_B]?.toSignificant(6)}</TYPE.body>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<TYPE.body>Rates</TYPE.body>
|
||||
<TYPE.body>
|
||||
{`1 ${tokens[Field.TOKEN_A]?.symbol} = ${price?.toSignificant(4)} ${tokens[Field.TOKEN_B]?.symbol}`}
|
||||
</TYPE.body>
|
||||
</RowBetween>
|
||||
<RowBetween style={{ justifyContent: 'flex-end' }}>
|
||||
<TYPE.body>
|
||||
{`1 ${tokens[Field.TOKEN_B]?.symbol} = ${price?.invert().toSignificant(4)} ${
|
||||
tokens[Field.TOKEN_A]?.symbol
|
||||
}`}
|
||||
</TYPE.body>
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<TYPE.body>Share of Pool:</TYPE.body>
|
||||
<TYPE.body>{noLiquidity ? '100' : poolTokenPercentage?.toSignificant(4)}%</TYPE.body>
|
||||
</RowBetween>
|
||||
<ButtonPrimary style={{ margin: '20px 0 0 0' }} onClick={onAdd}>
|
||||
<Text fontWeight={500} fontSize={20}>
|
||||
{noLiquidity ? 'Create Pool & Supply' : 'Confirm Supply'}
|
||||
</Text>
|
||||
</ButtonPrimary>
|
||||
</>
|
||||
<ConfirmAddModalBottom
|
||||
price={price}
|
||||
currencies={currencies}
|
||||
parsedAmounts={parsedAmounts}
|
||||
noLiquidity={noLiquidity}
|
||||
onAdd={onAdd}
|
||||
poolTokenPercentage={poolTokenPercentage}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const PriceBar = () => {
|
||||
return (
|
||||
<AutoColumn gap="md">
|
||||
<AutoRow justify="space-around" gap="4px">
|
||||
<AutoColumn justify="center">
|
||||
<TYPE.black>{price?.toSignificant(6) ?? '0'}</TYPE.black>
|
||||
<Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}>
|
||||
{tokens[Field.TOKEN_B]?.symbol} per {tokens[Field.TOKEN_A]?.symbol}
|
||||
</Text>
|
||||
</AutoColumn>
|
||||
<AutoColumn justify="center">
|
||||
<TYPE.black>{price?.invert().toSignificant(6) ?? '0'}</TYPE.black>
|
||||
<Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}>
|
||||
{tokens[Field.TOKEN_A]?.symbol} per {tokens[Field.TOKEN_B]?.symbol}
|
||||
</Text>
|
||||
</AutoColumn>
|
||||
<AutoColumn justify="center">
|
||||
<TYPE.black>
|
||||
{noLiquidity && price
|
||||
? '100'
|
||||
: (poolTokenPercentage?.lessThan(ONE_BIPS) ? '<0.01' : poolTokenPercentage?.toFixed(2)) ?? '0'}
|
||||
%
|
||||
</TYPE.black>
|
||||
<Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}>
|
||||
Share of Pool
|
||||
</Text>
|
||||
</AutoColumn>
|
||||
</AutoRow>
|
||||
</AutoColumn>
|
||||
)
|
||||
}
|
||||
const pendingText = `Supplying ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
|
||||
currencies[Field.CURRENCY_A]?.symbol
|
||||
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencies[Field.CURRENCY_B]?.symbol}`
|
||||
|
||||
const pendingText = `Supplying ${parsedAmounts[Field.TOKEN_A]?.toSignificant(6)} ${
|
||||
tokens[Field.TOKEN_A]?.symbol
|
||||
} and ${parsedAmounts[Field.TOKEN_B]?.toSignificant(6)} ${tokens[Field.TOKEN_B]?.symbol}`
|
||||
const handleCurrencyASelect = useCallback(
|
||||
(currencyA: Currency) => {
|
||||
const newCurrencyIdA = currencyId(currencyA)
|
||||
if (newCurrencyIdA === currencyIdB) {
|
||||
history.push(`/add/${currencyIdB}/${currencyIdA}`)
|
||||
} else {
|
||||
history.push(`/add/${newCurrencyIdA}/${currencyIdB}`)
|
||||
}
|
||||
},
|
||||
[currencyIdB, history, currencyIdA]
|
||||
)
|
||||
const handleCurrencyBSelect = useCallback(
|
||||
(currencyB: Currency) => {
|
||||
const newCurrencyIdB = currencyId(currencyB)
|
||||
if (currencyIdA === newCurrencyIdB) {
|
||||
if (currencyIdB) {
|
||||
history.push(`/add/${currencyIdB}/${newCurrencyIdB}`)
|
||||
} else {
|
||||
history.push(`/add/${newCurrencyIdB}`)
|
||||
}
|
||||
} else {
|
||||
history.push(`/add/${currencyIdA ? currencyIdA : 'ETH'}/${newCurrencyIdB}`)
|
||||
}
|
||||
},
|
||||
[currencyIdA, history, currencyIdB]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppBody>
|
||||
<AddRemoveTabs adding={true} />
|
||||
<Wrapper>
|
||||
<ConfirmationModal
|
||||
isOpen={showConfirm}
|
||||
@@ -314,7 +305,7 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
|
||||
setShowConfirm(false)
|
||||
// if there was a tx hash, we want to clear the input
|
||||
if (txHash) {
|
||||
onUserInput(Field.TOKEN_A, '')
|
||||
onFieldAInput('')
|
||||
}
|
||||
setTxHash('')
|
||||
}}
|
||||
@@ -344,36 +335,33 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
|
||||
</ColumnCenter>
|
||||
)}
|
||||
<CurrencyInputPanel
|
||||
disableTokenSelect={true}
|
||||
field={Field.TOKEN_A}
|
||||
value={formattedAmounts[Field.TOKEN_A]}
|
||||
onUserInput={onUserInput}
|
||||
value={formattedAmounts[Field.CURRENCY_A]}
|
||||
onUserInput={onFieldAInput}
|
||||
onMax={() => {
|
||||
maxAmounts[Field.TOKEN_A] && onUserInput(Field.TOKEN_A, maxAmounts[Field.TOKEN_A].toExact())
|
||||
onFieldAInput(maxAmounts[Field.CURRENCY_A]?.toExact() ?? '')
|
||||
}}
|
||||
showMaxButton={!atMaxAmounts[Field.TOKEN_A]}
|
||||
token={tokens[Field.TOKEN_A]}
|
||||
pair={pair}
|
||||
label="Input"
|
||||
onCurrencySelect={handleCurrencyASelect}
|
||||
showMaxButton={!atMaxAmounts[Field.CURRENCY_A]}
|
||||
currency={currencies[Field.CURRENCY_A]}
|
||||
id="add-liquidity-input-tokena"
|
||||
showCommonBases
|
||||
/>
|
||||
<ColumnCenter>
|
||||
<Plus size="16" color={theme.text2} />
|
||||
</ColumnCenter>
|
||||
<CurrencyInputPanel
|
||||
disableTokenSelect={true}
|
||||
field={Field.TOKEN_B}
|
||||
value={formattedAmounts[Field.TOKEN_B]}
|
||||
onUserInput={onUserInput}
|
||||
value={formattedAmounts[Field.CURRENCY_B]}
|
||||
onUserInput={onFieldBInput}
|
||||
onCurrencySelect={handleCurrencyBSelect}
|
||||
onMax={() => {
|
||||
maxAmounts[Field.TOKEN_B] && onUserInput(Field.TOKEN_B, maxAmounts[Field.TOKEN_B].toExact())
|
||||
onFieldBInput(maxAmounts[Field.CURRENCY_B]?.toExact() ?? '')
|
||||
}}
|
||||
showMaxButton={!atMaxAmounts[Field.TOKEN_B]}
|
||||
token={tokens[Field.TOKEN_B]}
|
||||
pair={pair}
|
||||
showMaxButton={!atMaxAmounts[Field.CURRENCY_B]}
|
||||
currency={currencies[Field.CURRENCY_B]}
|
||||
id="add-liquidity-input-tokenb"
|
||||
showCommonBases
|
||||
/>
|
||||
{tokens[Field.TOKEN_A] && tokens[Field.TOKEN_B] && (
|
||||
{currencies[Field.CURRENCY_A] && currencies[Field.CURRENCY_B] && pairState !== PairState.INVALID && (
|
||||
<>
|
||||
<GreyCard padding="0px" borderRadius={'20px'}>
|
||||
<RowBetween padding="1rem">
|
||||
@@ -382,7 +370,12 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
|
||||
</TYPE.subHeader>
|
||||
</RowBetween>{' '}
|
||||
<LightCard padding="1rem" borderRadius={'20px'}>
|
||||
<PriceBar />
|
||||
<PoolPriceBar
|
||||
currencies={currencies}
|
||||
poolTokenPercentage={poolTokenPercentage}
|
||||
noLiquidity={noLiquidity}
|
||||
price={price}
|
||||
/>
|
||||
</LightCard>
|
||||
</GreyCard>
|
||||
</>
|
||||
@@ -405,9 +398,9 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
|
||||
width={approvalB !== ApprovalState.APPROVED ? '48%' : '100%'}
|
||||
>
|
||||
{approvalA === ApprovalState.PENDING ? (
|
||||
<Dots>Approving {tokens[Field.TOKEN_A]?.symbol}</Dots>
|
||||
<Dots>Approving {currencies[Field.CURRENCY_A]?.symbol}</Dots>
|
||||
) : (
|
||||
'Approve ' + tokens[Field.TOKEN_A]?.symbol
|
||||
'Approve ' + currencies[Field.CURRENCY_A]?.symbol
|
||||
)}
|
||||
</ButtonPrimary>
|
||||
)}
|
||||
@@ -418,9 +411,9 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
|
||||
width={approvalA !== ApprovalState.APPROVED ? '48%' : '100%'}
|
||||
>
|
||||
{approvalB === ApprovalState.PENDING ? (
|
||||
<Dots>Approving {tokens[Field.TOKEN_B]?.symbol}</Dots>
|
||||
<Dots>Approving {currencies[Field.CURRENCY_B]?.symbol}</Dots>
|
||||
) : (
|
||||
'Approve ' + tokens[Field.TOKEN_B]?.symbol
|
||||
'Approve ' + currencies[Field.CURRENCY_B]?.symbol
|
||||
)}
|
||||
</ButtonPrimary>
|
||||
)}
|
||||
@@ -431,7 +424,7 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
|
||||
expertMode ? onAdd() : setShowConfirm(true)
|
||||
}}
|
||||
disabled={!isValid || approvalA !== ApprovalState.APPROVED || approvalB !== ApprovalState.APPROVED}
|
||||
error={!isValid && !!parsedAmounts[Field.TOKEN_A] && !!parsedAmounts[Field.TOKEN_B]}
|
||||
error={!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]}
|
||||
>
|
||||
<Text fontSize={20} fontWeight={500}>
|
||||
{error ?? 'Supply'}
|
||||
@@ -443,9 +436,9 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
|
||||
</Wrapper>
|
||||
</AppBody>
|
||||
|
||||
{pair && !noLiquidity ? (
|
||||
{pair && !noLiquidity && pairState !== PairState.INVALID ? (
|
||||
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
|
||||
<PositionCard pair={pair} minimal={true} />
|
||||
<MinimalPositionCard showUnwrapped={oneCurrencyIsWETH} pair={pair} />
|
||||
</AutoColumn>
|
||||
) : null}
|
||||
</>
|
||||
|
||||
34
src/pages/AddLiquidity/redirects.tsx
Normal file
34
src/pages/AddLiquidity/redirects.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import React from 'react'
|
||||
import { Redirect, RouteComponentProps } from 'react-router-dom'
|
||||
import AddLiquidity from './index'
|
||||
|
||||
export function RedirectToAddLiquidity() {
|
||||
return <Redirect to="/add/" />
|
||||
}
|
||||
|
||||
const OLD_PATH_STRUCTURE = /^(0x[a-fA-F0-9]{40})-(0x[a-fA-F0-9]{40})$/
|
||||
export function RedirectOldAddLiquidityPathStructure(props: RouteComponentProps<{ currencyIdA: string }>) {
|
||||
const {
|
||||
match: {
|
||||
params: { currencyIdA }
|
||||
}
|
||||
} = props
|
||||
const match = currencyIdA.match(OLD_PATH_STRUCTURE)
|
||||
if (match?.length) {
|
||||
return <Redirect to={`/add/${match[1]}/${match[2]}`} />
|
||||
}
|
||||
|
||||
return <AddLiquidity {...props} />
|
||||
}
|
||||
|
||||
export function RedirectDuplicateTokenIds(props: RouteComponentProps<{ currencyIdA: string; currencyIdB: string }>) {
|
||||
const {
|
||||
match: {
|
||||
params: { currencyIdA, currencyIdB }
|
||||
}
|
||||
} = props
|
||||
if (currencyIdA.toLowerCase() === currencyIdB.toLowerCase()) {
|
||||
return <Redirect to={`/add/${currencyIdA}`} />
|
||||
}
|
||||
return <AddLiquidity {...props} />
|
||||
}
|
||||
7
src/pages/AddLiquidity/tsconfig.json
Normal file
7
src/pages/AddLiquidity/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.strict.json",
|
||||
"include": [
|
||||
"**/*",
|
||||
"../../../node_modules/eslint-plugin-react/lib/types.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -1,21 +1,24 @@
|
||||
import React, { Suspense } from 'react'
|
||||
import { BrowserRouter, HashRouter, Route, Switch } from 'react-router-dom'
|
||||
import { HashRouter, Route, Switch } from 'react-router-dom'
|
||||
import styled from 'styled-components'
|
||||
import GoogleAnalyticsReporter from '../components/analytics/GoogleAnalyticsReporter'
|
||||
import Footer from '../components/Footer'
|
||||
import Header from '../components/Header'
|
||||
import Popups from '../components/Popups'
|
||||
import Web3ReactManager from '../components/Web3ReactManager'
|
||||
import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader'
|
||||
import AddLiquidity from './AddLiquidity'
|
||||
import CreatePool from './CreatePool'
|
||||
import {
|
||||
RedirectDuplicateTokenIds,
|
||||
RedirectOldAddLiquidityPathStructure,
|
||||
RedirectToAddLiquidity
|
||||
} from './AddLiquidity/redirects'
|
||||
import MigrateV1 from './MigrateV1'
|
||||
import MigrateV1Exchange from './MigrateV1/MigrateV1Exchange'
|
||||
import RemoveV1Exchange from './MigrateV1/RemoveV1Exchange'
|
||||
import Pool from './Pool'
|
||||
import PoolFinder from './PoolFinder'
|
||||
import RemoveLiquidity from './RemoveLiquidity'
|
||||
import Send from './Send'
|
||||
import { RedirectOldRemoveLiquidityPathStructure } from './RemoveLiquidity/redirects'
|
||||
import Swap from './Swap'
|
||||
import { RedirectPathToSwapOnly, RedirectToSwap } from './Swap/redirects'
|
||||
|
||||
@@ -54,17 +57,10 @@ const Marginer = styled.div`
|
||||
margin-top: 5rem;
|
||||
`
|
||||
|
||||
let Router: React.ComponentType
|
||||
if (process.env.PUBLIC_URL === '.') {
|
||||
Router = HashRouter
|
||||
} else {
|
||||
Router = BrowserRouter
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<Router>
|
||||
<HashRouter>
|
||||
<Route component={GoogleAnalyticsReporter} />
|
||||
<Route component={DarkModeQueryParamReader} />
|
||||
<AppWrapper>
|
||||
@@ -77,23 +73,25 @@ export default function App() {
|
||||
<Switch>
|
||||
<Route exact strict path="/swap" component={Swap} />
|
||||
<Route exact strict path="/swap/:outputCurrency" component={RedirectToSwap} />
|
||||
<Route exact strict path="/send" component={Send} />
|
||||
<Route exact strict path="/send" component={RedirectPathToSwapOnly} />
|
||||
<Route exact strict path="/find" component={PoolFinder} />
|
||||
<Route exact strict path="/pool" component={Pool} />
|
||||
<Route exact strict path="/create" component={CreatePool} />
|
||||
<Route exact strict path="/add/:tokens" component={AddLiquidity} />
|
||||
<Route exact strict path="/remove/:tokens" component={RemoveLiquidity} />
|
||||
<Route exact strict path="/create" component={RedirectToAddLiquidity} />
|
||||
<Route exact path="/add" component={AddLiquidity} />
|
||||
<Route exact path="/add/:currencyIdA" component={RedirectOldAddLiquidityPathStructure} />
|
||||
<Route exact path="/add/:currencyIdA/:currencyIdB" component={RedirectDuplicateTokenIds} />
|
||||
<Route exact strict path="/remove/v1/:address" component={RemoveV1Exchange} />
|
||||
<Route exact strict path="/remove/:tokens" component={RedirectOldRemoveLiquidityPathStructure} />
|
||||
<Route exact strict path="/remove/:currencyIdA/:currencyIdB" component={RemoveLiquidity} />
|
||||
<Route exact strict path="/migrate/v1" component={MigrateV1} />
|
||||
<Route exact strict path="/migrate/v1/:address" component={MigrateV1Exchange} />
|
||||
<Route exact strict path="/remove/v1/:address" component={RemoveV1Exchange} />
|
||||
<Route component={RedirectPathToSwapOnly} />
|
||||
</Switch>
|
||||
</Web3ReactManager>
|
||||
<Marginer />
|
||||
<Footer />
|
||||
</BodyWrapper>
|
||||
</AppWrapper>
|
||||
</Router>
|
||||
</HashRouter>
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import NavigationTabs from '../components/NavigationTabs'
|
||||
|
||||
export const BodyWrapper = styled.div`
|
||||
position: relative;
|
||||
@@ -17,10 +16,5 @@ export const BodyWrapper = styled.div`
|
||||
* The styled container element that wraps the content of most pages and the tabs.
|
||||
*/
|
||||
export default function AppBody({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<BodyWrapper>
|
||||
<NavigationTabs />
|
||||
<>{children}</>
|
||||
</BodyWrapper>
|
||||
)
|
||||
return <BodyWrapper>{children}</BodyWrapper>
|
||||
}
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { RouteComponentProps, Redirect } from 'react-router-dom'
|
||||
import { Token, WETH } from '@uniswap/sdk'
|
||||
import AppBody from '../AppBody'
|
||||
|
||||
import Row, { AutoRow } from '../../components/Row'
|
||||
import TokenLogo from '../../components/TokenLogo'
|
||||
import TokenSearchModal from '../../components/SearchModal/TokenSearchModal'
|
||||
import { Text } from 'rebass'
|
||||
import { Plus } from 'react-feather'
|
||||
import { TYPE, StyledInternalLink } from '../../theme'
|
||||
import { AutoColumn, ColumnCenter } from '../../components/Column'
|
||||
import { ButtonPrimary, ButtonDropdown, ButtonDropdownLight } from '../../components/Button'
|
||||
|
||||
import { useToken } from '../../hooks/Tokens'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { usePair } from '../../data/Reserves'
|
||||
|
||||
enum Fields {
|
||||
TOKEN0 = 0,
|
||||
TOKEN1 = 1
|
||||
}
|
||||
|
||||
enum STEP {
|
||||
SELECT_TOKENS = 'SELECT_TOKENS', // choose input and output tokens
|
||||
READY_TO_CREATE = 'READY_TO_CREATE', // enable 'create' button
|
||||
SHOW_CREATE_PAGE = 'SHOW_CREATE_PAGE' // show create page
|
||||
}
|
||||
|
||||
export default function CreatePool({ location }: RouteComponentProps) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const [showSearch, setShowSearch] = useState<boolean>(false)
|
||||
const [activeField, setActiveField] = useState<number>(Fields.TOKEN0)
|
||||
|
||||
const [token0Address, setToken0Address] = useState<string>(WETH[chainId].address)
|
||||
const [token1Address, setToken1Address] = useState<string>()
|
||||
|
||||
const token0: Token = useToken(token0Address)
|
||||
const token1: Token = useToken(token1Address)
|
||||
|
||||
const [step, setStep] = useState<string>(STEP.SELECT_TOKENS)
|
||||
|
||||
const pair = usePair(token0, token1)
|
||||
|
||||
// if both tokens selected but pair doesnt exist, enable button to create pair
|
||||
useEffect(() => {
|
||||
if (token0Address && token1Address && pair === null) {
|
||||
setStep(STEP.READY_TO_CREATE)
|
||||
}
|
||||
}, [pair, token0Address, token1Address])
|
||||
|
||||
// if theyve clicked create, show add liquidity page
|
||||
if (step === STEP.SHOW_CREATE_PAGE) {
|
||||
return <Redirect to={{ ...location, pathname: `/add/${token0Address}-${token1Address}` }} push={true} />
|
||||
}
|
||||
|
||||
return (
|
||||
<AppBody>
|
||||
<AutoColumn gap="20px">
|
||||
<AutoColumn gap="24px">
|
||||
{!token0Address ? (
|
||||
<ButtonDropdown
|
||||
onClick={() => {
|
||||
setShowSearch(true)
|
||||
setActiveField(Fields.TOKEN0)
|
||||
}}
|
||||
>
|
||||
<Text fontSize={20}>Select first token</Text>
|
||||
</ButtonDropdown>
|
||||
) : (
|
||||
<ButtonDropdownLight
|
||||
onClick={() => {
|
||||
setShowSearch(true)
|
||||
setActiveField(Fields.TOKEN0)
|
||||
}}
|
||||
>
|
||||
<Row align="flex-end">
|
||||
<TokenLogo address={token0Address} />
|
||||
<Text fontWeight={500} fontSize={20} marginLeft={'12px'}>
|
||||
{token0?.symbol}{' '}
|
||||
</Text>
|
||||
<TYPE.darkGray fontWeight={500} fontSize={16} marginLeft={'8px'}>
|
||||
{token0?.address === WETH[chainId]?.address && '(default)'}
|
||||
</TYPE.darkGray>
|
||||
</Row>
|
||||
</ButtonDropdownLight>
|
||||
)}
|
||||
<ColumnCenter>
|
||||
<Plus size="16" color="#888D9B" />
|
||||
</ColumnCenter>
|
||||
{!token1Address ? (
|
||||
<ButtonDropdown
|
||||
onClick={() => {
|
||||
setShowSearch(true)
|
||||
setActiveField(Fields.TOKEN1)
|
||||
}}
|
||||
disabled={step !== STEP.SELECT_TOKENS}
|
||||
>
|
||||
<Text fontSize={20}>Select second token</Text>
|
||||
</ButtonDropdown>
|
||||
) : (
|
||||
<ButtonDropdownLight
|
||||
onClick={() => {
|
||||
setShowSearch(true)
|
||||
setActiveField(Fields.TOKEN1)
|
||||
}}
|
||||
>
|
||||
<Row>
|
||||
<TokenLogo address={token1Address} />
|
||||
<Text fontWeight={500} fontSize={20} marginLeft={'12px'}>
|
||||
{token1?.symbol}
|
||||
</Text>
|
||||
</Row>
|
||||
</ButtonDropdownLight>
|
||||
)}
|
||||
{pair ? ( // pair already exists - prompt to add liquidity to existing pool
|
||||
<AutoRow padding="10px" justify="center">
|
||||
<TYPE.body textAlign="center">
|
||||
Pool already exists!{' '}
|
||||
<StyledInternalLink to={`/add/${token0Address}-${token1Address}`}>Join the pool.</StyledInternalLink>
|
||||
</TYPE.body>
|
||||
</AutoRow>
|
||||
) : (
|
||||
<ButtonPrimary disabled={step !== STEP.READY_TO_CREATE} onClick={() => setStep(STEP.SHOW_CREATE_PAGE)}>
|
||||
<Text fontWeight={500} fontSize={20}>
|
||||
Create Pool
|
||||
</Text>
|
||||
</ButtonPrimary>
|
||||
)}
|
||||
</AutoColumn>
|
||||
<TokenSearchModal
|
||||
isOpen={showSearch}
|
||||
onTokenSelect={address => {
|
||||
activeField === Fields.TOKEN0 ? setToken0Address(address) : setToken1Address(address)
|
||||
}}
|
||||
onDismiss={() => {
|
||||
setShowSearch(false)
|
||||
}}
|
||||
hiddenToken={activeField === Fields.TOKEN0 ? token1Address : token0Address}
|
||||
showCommonBases={activeField === Fields.TOKEN0}
|
||||
/>
|
||||
</AutoColumn>
|
||||
</AppBody>
|
||||
)
|
||||
}
|
||||
@@ -1,18 +1,20 @@
|
||||
import { TransactionResponse } from '@ethersproject/abstract-provider'
|
||||
import { ChainId, Fraction, JSBI, Percent, Token, TokenAmount, WETH } from '@uniswap/sdk'
|
||||
import { AddressZero } from '@ethersproject/constants'
|
||||
import { Currency, CurrencyAmount, Fraction, JSBI, Percent, Token, TokenAmount, WETH } from '@uniswap/sdk'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { ArrowLeft } from 'react-feather'
|
||||
import ReactGA from 'react-ga'
|
||||
import { Redirect, RouteComponentProps } from 'react-router'
|
||||
import { Text } from 'rebass'
|
||||
import { ButtonConfirmed } from '../../components/Button'
|
||||
import { PinkCard, YellowCard, LightCard } from '../../components/Card'
|
||||
import { LightCard, PinkCard, YellowCard } from '../../components/Card'
|
||||
import { AutoColumn } from '../../components/Column'
|
||||
import CurrencyLogo from '../../components/CurrencyLogo'
|
||||
import QuestionHelper from '../../components/QuestionHelper'
|
||||
import { AutoRow, RowBetween, RowFixed } from '../../components/Row'
|
||||
import { Dots } from '../../components/swap/styleds'
|
||||
import { DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE } from '../../constants'
|
||||
import { MIGRATOR_ADDRESS } from '../../constants/abis/migrator'
|
||||
import { usePair } from '../../data/Reserves'
|
||||
import { PairState, usePair } from '../../data/Reserves'
|
||||
import { useTotalSupply } from '../../data/TotalSupply'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useToken } from '../../hooks/Tokens'
|
||||
@@ -21,29 +23,26 @@ import { useV1ExchangeContract, useV2MigratorContract } from '../../hooks/useCon
|
||||
import { NEVER_RELOAD, useSingleCallResult } from '../../state/multicall/hooks'
|
||||
import { useIsTransactionPending, useTransactionAdder } from '../../state/transactions/hooks'
|
||||
import { useETHBalances, useTokenBalance } from '../../state/wallet/hooks'
|
||||
import { TYPE, ExternalLink } from '../../theme'
|
||||
import { isAddress, getEtherscanLink } from '../../utils'
|
||||
import { BackArrow, ExternalLink, TYPE } from '../../theme'
|
||||
import { getEtherscanLink, isAddress } from '../../utils'
|
||||
import { BodyWrapper } from '../AppBody'
|
||||
import { EmptyState } from './EmptyState'
|
||||
import TokenLogo from '../../components/TokenLogo'
|
||||
import { AddressZero } from '@ethersproject/constants'
|
||||
import { Text } from 'rebass'
|
||||
|
||||
const POOL_TOKEN_AMOUNT_MIN = new Fraction(JSBI.BigInt(1), JSBI.BigInt(1000000))
|
||||
const POOL_CURRENCY_AMOUNT_MIN = new Fraction(JSBI.BigInt(1), JSBI.BigInt(1000000))
|
||||
const WEI_DENOM = JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(18))
|
||||
const ZERO = JSBI.BigInt(0)
|
||||
const ONE = JSBI.BigInt(1)
|
||||
const ZERO_FRACTION = new Fraction(ZERO, ONE)
|
||||
const ALLOWED_OUTPUT_MIN_PERCENT = new Percent(JSBI.BigInt(10000 - INITIAL_ALLOWED_SLIPPAGE), JSBI.BigInt(10000))
|
||||
|
||||
function FormattedPoolTokenAmount({ tokenAmount }: { tokenAmount: TokenAmount }) {
|
||||
function FormattedPoolCurrencyAmount({ currencyAmount }: { currencyAmount: CurrencyAmount }) {
|
||||
return (
|
||||
<>
|
||||
{tokenAmount.equalTo(JSBI.BigInt(0))
|
||||
{currencyAmount.equalTo(JSBI.BigInt(0))
|
||||
? '0'
|
||||
: tokenAmount.greaterThan(POOL_TOKEN_AMOUNT_MIN)
|
||||
? tokenAmount.toSignificant(4)
|
||||
: `<${POOL_TOKEN_AMOUNT_MIN.toSignificant(1)}`}
|
||||
: currencyAmount.greaterThan(POOL_CURRENCY_AMOUNT_MIN)
|
||||
? currencyAmount.toSignificant(4)
|
||||
: `<${POOL_CURRENCY_AMOUNT_MIN.toSignificant(1)}`}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -57,17 +56,17 @@ export function V1LiquidityInfo({
|
||||
token: Token
|
||||
liquidityTokenAmount: TokenAmount
|
||||
tokenWorth: TokenAmount
|
||||
ethWorth: Fraction
|
||||
ethWorth: CurrencyAmount
|
||||
}) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
|
||||
return (
|
||||
<>
|
||||
<AutoRow style={{ justifyContent: 'flex-start', width: 'fit-content' }}>
|
||||
<TokenLogo size="24px" address={token.address} />
|
||||
<CurrencyLogo size="24px" currency={token} />
|
||||
<div style={{ marginLeft: '.75rem' }}>
|
||||
<TYPE.mediumHeader>
|
||||
{<FormattedPoolTokenAmount tokenAmount={liquidityTokenAmount} />}{' '}
|
||||
{<FormattedPoolCurrencyAmount currencyAmount={liquidityTokenAmount} />}{' '}
|
||||
{token.equals(WETH[chainId]) ? 'WETH' : token.symbol}/ETH
|
||||
</TYPE.mediumHeader>
|
||||
</div>
|
||||
@@ -81,7 +80,7 @@ export function V1LiquidityInfo({
|
||||
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
|
||||
{tokenWorth.toSignificant(4)}
|
||||
</Text>
|
||||
<TokenLogo size="20px" style={{ marginLeft: '8px' }} address={token.address} />
|
||||
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={token} />
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
<RowBetween mb="1rem">
|
||||
@@ -90,9 +89,9 @@ export function V1LiquidityInfo({
|
||||
</Text>
|
||||
<RowFixed>
|
||||
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
|
||||
{ethWorth.toSignificant(4)}
|
||||
<FormattedPoolCurrencyAmount currencyAmount={ethWorth} />
|
||||
</Text>
|
||||
<TokenLogo size="20px" style={{ marginLeft: '8px' }} address={WETH[chainId].address} />
|
||||
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={Currency.ETHER} />
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
</>
|
||||
@@ -105,19 +104,19 @@ function V1PairMigration({ liquidityTokenAmount, token }: { liquidityTokenAmount
|
||||
const exchangeETHBalance = useETHBalances([liquidityTokenAmount.token.address])?.[liquidityTokenAmount.token.address]
|
||||
const exchangeTokenBalance = useTokenBalance(liquidityTokenAmount.token.address, token)
|
||||
|
||||
const v2Pair = usePair(WETH[chainId as ChainId], token)
|
||||
const isFirstLiquidityProvider: boolean = v2Pair === null
|
||||
const [v2PairState, v2Pair] = usePair(chainId ? WETH[chainId] : undefined, token)
|
||||
const isFirstLiquidityProvider: boolean = v2PairState === PairState.NOT_EXISTS
|
||||
|
||||
const v2SpotPrice = v2Pair?.reserveOf(token)?.divide(v2Pair?.reserveOf(WETH[chainId as ChainId]))
|
||||
const v2SpotPrice = v2Pair?.reserveOf(token)?.divide(v2Pair?.reserveOf(WETH[chainId]))
|
||||
|
||||
const [confirmingMigration, setConfirmingMigration] = useState<boolean>(false)
|
||||
const [pendingMigrationHash, setPendingMigrationHash] = useState<string | null>(null)
|
||||
|
||||
const shareFraction: Fraction = totalSupply ? new Percent(liquidityTokenAmount.raw, totalSupply.raw) : ZERO_FRACTION
|
||||
|
||||
const ethWorth: Fraction = exchangeETHBalance
|
||||
? new Fraction(shareFraction.multiply(exchangeETHBalance).quotient, WEI_DENOM)
|
||||
: ZERO_FRACTION
|
||||
const ethWorth: CurrencyAmount = exchangeETHBalance
|
||||
? CurrencyAmount.ether(exchangeETHBalance.multiply(shareFraction).multiply(WEI_DENOM).quotient)
|
||||
: CurrencyAmount.ether(ZERO)
|
||||
|
||||
const tokenWorth: TokenAmount = exchangeTokenBalance
|
||||
? new TokenAmount(token, shareFraction.multiply(exchangeTokenBalance.raw).quotient)
|
||||
@@ -127,7 +126,7 @@ function V1PairMigration({ liquidityTokenAmount, token }: { liquidityTokenAmount
|
||||
|
||||
const v1SpotPrice =
|
||||
exchangeTokenBalance && exchangeETHBalance
|
||||
? exchangeTokenBalance.divide(new Fraction(exchangeETHBalance, WEI_DENOM))
|
||||
? exchangeTokenBalance.divide(new Fraction(exchangeETHBalance.raw, WEI_DENOM))
|
||||
: null
|
||||
|
||||
const priceDifferenceFraction: Fraction | undefined =
|
||||
@@ -344,10 +343,6 @@ export default function MigrateV1Exchange({
|
||||
)
|
||||
const userLiquidityBalance = useTokenBalance(account, liquidityToken)
|
||||
|
||||
const handleBack = useCallback(() => {
|
||||
history.push('/migrate/v1')
|
||||
}, [history])
|
||||
|
||||
// redirect for invalid url params
|
||||
if (!validatedAddress || tokenAddress === AddressZero) {
|
||||
console.error('Invalid address in path', address)
|
||||
@@ -358,9 +353,7 @@ export default function MigrateV1Exchange({
|
||||
<BodyWrapper style={{ padding: 24 }}>
|
||||
<AutoColumn gap="16px">
|
||||
<AutoRow style={{ alignItems: 'center', justifyContent: 'space-between' }} gap="8px">
|
||||
<div style={{ cursor: 'pointer' }}>
|
||||
<ArrowLeft onClick={handleBack} />
|
||||
</div>
|
||||
<BackArrow to="/migrate/v1" />
|
||||
<TYPE.mediumHeader>Migrate V1 Liquidity</TYPE.mediumHeader>
|
||||
<div>
|
||||
<QuestionHelper text="Migrate your liquidity tokens from Uniswap V1 to Uniswap V2." />
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { TransactionResponse } from '@ethersproject/abstract-provider'
|
||||
import { JSBI, Token, TokenAmount, WETH, Fraction, Percent } from '@uniswap/sdk'
|
||||
import { JSBI, Token, TokenAmount, WETH, Fraction, Percent, CurrencyAmount } from '@uniswap/sdk'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { ArrowLeft } from 'react-feather'
|
||||
import ReactGA from 'react-ga'
|
||||
import { Redirect, RouteComponentProps } from 'react-router'
|
||||
import { ButtonConfirmed } from '../../components/Button'
|
||||
@@ -16,7 +15,7 @@ import { useV1ExchangeContract } from '../../hooks/useContract'
|
||||
import { NEVER_RELOAD, useSingleCallResult } from '../../state/multicall/hooks'
|
||||
import { useIsTransactionPending, useTransactionAdder } from '../../state/transactions/hooks'
|
||||
import { useTokenBalance, useETHBalances } from '../../state/wallet/hooks'
|
||||
import { TYPE } from '../../theme'
|
||||
import { BackArrow, TYPE } from '../../theme'
|
||||
import { isAddress } from '../../utils'
|
||||
import { BodyWrapper } from '../AppBody'
|
||||
import { EmptyState } from './EmptyState'
|
||||
@@ -50,9 +49,9 @@ function V1PairRemoval({
|
||||
|
||||
const shareFraction: Fraction = totalSupply ? new Percent(liquidityTokenAmount.raw, totalSupply.raw) : ZERO_FRACTION
|
||||
|
||||
const ethWorth: Fraction = exchangeETHBalance
|
||||
? new Fraction(shareFraction.multiply(exchangeETHBalance).quotient, WEI_DENOM)
|
||||
: ZERO_FRACTION
|
||||
const ethWorth: CurrencyAmount = exchangeETHBalance
|
||||
? CurrencyAmount.ether(exchangeETHBalance.multiply(shareFraction).multiply(WEI_DENOM).quotient)
|
||||
: CurrencyAmount.ether(ZERO)
|
||||
|
||||
const tokenWorth: TokenAmount = exchangeTokenBalance
|
||||
? new TokenAmount(token, shareFraction.multiply(exchangeTokenBalance.raw).quotient)
|
||||
@@ -128,7 +127,6 @@ function V1PairRemoval({
|
||||
}
|
||||
|
||||
export default function RemoveV1Exchange({
|
||||
history,
|
||||
match: {
|
||||
params: { address }
|
||||
}
|
||||
@@ -149,10 +147,6 @@ export default function RemoveV1Exchange({
|
||||
)
|
||||
const userLiquidityBalance = useTokenBalance(account, liquidityToken)
|
||||
|
||||
const handleBack = useCallback(() => {
|
||||
history.push('/migrate/v1')
|
||||
}, [history])
|
||||
|
||||
// redirect for invalid url params
|
||||
if (!validatedAddress || tokenAddress === AddressZero) {
|
||||
console.error('Invalid address in path', address)
|
||||
@@ -160,12 +154,10 @@ export default function RemoveV1Exchange({
|
||||
}
|
||||
|
||||
return (
|
||||
<BodyWrapper style={{ padding: 24 }}>
|
||||
<BodyWrapper style={{ padding: 24 }} id="remove-v1-exchange">
|
||||
<AutoColumn gap="16px">
|
||||
<AutoRow style={{ alignItems: 'center', justifyContent: 'space-between' }} gap="8px">
|
||||
<div style={{ cursor: 'pointer' }}>
|
||||
<ArrowLeft onClick={handleBack} />
|
||||
</div>
|
||||
<BackArrow to="/migrate/v1" />
|
||||
<TYPE.mediumHeader>Remove V1 Liquidity</TYPE.mediumHeader>
|
||||
<div>
|
||||
<QuestionHelper text="Remove your Uniswap V1 liquidity tokens." />
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user