Compare commits
89 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83762b3c6a | ||
|
|
7784f647e1 | ||
|
|
65ba56c5f0 | ||
|
|
963514cd51 | ||
|
|
6d0c8037a8 | ||
|
|
d5ef7f9b24 | ||
|
|
f86db00464 | ||
|
|
8884020fab | ||
|
|
db145658db | ||
|
|
55bbaffe15 | ||
|
|
e1c3ad8f54 | ||
|
|
730af2eed4 | ||
|
|
c625d15feb | ||
|
|
f9de85251c | ||
|
|
a186833b7a | ||
|
|
aa06db7684 | ||
|
|
f5b601ee99 | ||
|
|
bf30013b6c | ||
|
|
2bc2a2c76e | ||
|
|
28bf95123e | ||
|
|
b1d88317b9 | ||
|
|
f332315de9 | ||
|
|
2fbb86ac44 | ||
|
|
8d567e4d4a | ||
|
|
77fbccd3f1 | ||
|
|
41330ab080 | ||
|
|
4ba9d98e67 | ||
|
|
52790ac92e | ||
|
|
380ca11dfd | ||
|
|
e4261cc01b | ||
|
|
99148dfd3f | ||
|
|
8bf9a9ce8d | ||
|
|
b6c0c1e607 | ||
|
|
0739613abe | ||
|
|
3401dfdebe | ||
|
|
ab9c27edad | ||
|
|
f2dfc57002 | ||
|
|
318252b6a9 | ||
|
|
440c50dd3a | ||
|
|
eacf4e6049 | ||
|
|
ffbd2d107a | ||
|
|
2db29f205b | ||
|
|
3ae9e2af2a | ||
|
|
5ff2cff841 | ||
|
|
0c0968864b | ||
|
|
9230473c36 | ||
|
|
b791afac59 | ||
|
|
2899c8af45 | ||
|
|
5f13f7824a | ||
|
|
6690bc513d | ||
|
|
aea3c1f6e6 | ||
|
|
e0c625671b | ||
|
|
aa3c867222 | ||
|
|
2182e676d4 | ||
|
|
f0fc11a3b5 | ||
|
|
53d93afd78 | ||
|
|
b612b15e1e | ||
|
|
9f5764aab3 | ||
|
|
f6dea47907 | ||
|
|
8e9981e186 | ||
|
|
1509c430fd | ||
|
|
93073321db | ||
|
|
618760ff1b | ||
|
|
2d20fc939d | ||
|
|
8c1e9b394b | ||
|
|
9b9f431e6f | ||
|
|
cfe590d810 | ||
|
|
2ee9b16c49 | ||
|
|
c52cd2c38e | ||
|
|
43cefbdc6a | ||
|
|
ae86fefe8b | ||
|
|
8901605c32 | ||
|
|
568829d3b5 | ||
|
|
b0f4b4c735 | ||
|
|
a73d3af4b2 | ||
|
|
2c7b431e60 | ||
|
|
0805b2d9b8 | ||
|
|
6c8fc5a7f6 | ||
|
|
702258574d | ||
|
|
17bc229a36 | ||
|
|
458a7d90a0 | ||
|
|
72877fb0b6 | ||
|
|
27f756107e | ||
|
|
abfd87c517 | ||
|
|
b3d772bdb5 | ||
|
|
d9c82ebf49 | ||
|
|
5298a5ce29 | ||
|
|
4d3073d2d9 | ||
|
|
c732f407c6 |
@@ -28,6 +28,18 @@
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"react/react-in-jsx-scope": "off"
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"paths": [
|
||||
{
|
||||
"name": "styled-components",
|
||||
"message": "Please import from styled-components/macro."
|
||||
}
|
||||
],
|
||||
"patterns": ["!styled-components/macro"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,52 @@
|
||||
# Development
|
||||
|
||||
## Install Dependencies
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
```
|
||||
|
||||
## Generate locale files
|
||||
|
||||
```
|
||||
yarn i18n:extract
|
||||
```
|
||||
|
||||
## Run the interface
|
||||
|
||||
```bash
|
||||
yarn start
|
||||
```
|
||||
|
||||
# Contributing
|
||||
|
||||
Thank you for your interest in contributing to the Uniswap interface! 🦄
|
||||
|
||||
# Development
|
||||
|
||||
## Running the interface locally
|
||||
|
||||
1. `yarn install`
|
||||
1. `yarn start`
|
||||
|
||||
## Creating a production build
|
||||
|
||||
1. `yarn install`
|
||||
1. `yarn build`
|
||||
|
||||
## Engineering standards
|
||||
|
||||
Code merged into the `main` branch of this repository should adhere to high standards of correctness and maintainability.
|
||||
Use your best judgment when applying these standards. If code is in the critical path, will be frequently visited, or
|
||||
makes large architectural changes, consider following all the standards.
|
||||
|
||||
- Have at least one engineer approve of large code refactorings
|
||||
- At least manually test small code changes, prefer automated tests
|
||||
- Thoroughly unit test when code is not obviously correct
|
||||
- Add integration tests for new pages or flows
|
||||
- Verify that all CI checks pass before merging
|
||||
- Have at least one product manager or designer approve of significant UX changes
|
||||
|
||||
## Guidelines
|
||||
|
||||
The following points should help guide your development:
|
||||
|
||||
- Security: the interface is safe to use
|
||||
- Avoid adding unnecessary dependencies due to [supply chain risk](https://github.com/LavaMoat/lavamoat#further-reading-on-software-supplychain-security)
|
||||
- Reproducibility: anyone can build the interface
|
||||
- Avoid adding steps to the development/build processes
|
||||
- The build must be deterministic, i.e. a particular commit hash always produces the same build
|
||||
- Decentralization: anyone can run the interface
|
||||
- An Ethereum node should be the only critical dependency
|
||||
- All other external dependencies should only enhance the UX ([graceful degradation](https://developer.mozilla.org/en-US/docs/Glossary/Graceful_degradation))
|
||||
- Accessibility: anyone can use the interface
|
||||
- The interface should be responsive, small and run well on low performance devices (majority of swaps on mobile!)
|
||||
|
||||
## Finding a first issue
|
||||
|
||||
Start with issues with the label
|
||||
[`good first issue`](https://github.com/Uniswap/uniswap-interface/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22).
|
||||
|
||||
## Pull requests
|
||||
|
||||
**Please open all pull requests against the `main` branch.**
|
||||
CI checks will run against all PRs.
|
||||
|
||||
# Translations
|
||||
|
||||
Help Uniswap reach a global audience!
|
||||
@@ -40,7 +55,7 @@ Uniswap uses [Crowdin](https://crowdin.com/project/uniswap-interface)
|
||||
for managing translations. Whenever a new string is added to the project,
|
||||
it gets uploaded to Crowdin for translation by [this workflow](./.github/workflows/crowdin.yaml).
|
||||
|
||||
Every hour, translations are synced from Crowdin in [this other workflow](./.github/workflows/crowdin-sync.yaml).
|
||||
Every hour, translations are synced from Crowdin to the repository in [this other workflow](./.github/workflows/crowdin-sync.yaml).
|
||||
|
||||
You can contribute by joining Crowdin to proofread existing translations [here](https://crowdin.com/project/uniswap-interface/invite?d=93i5n413q403t4g473p443o4c3t2g3s21343u2c3n403l4b3v2735353i4g4k4l4g453j4g4o4j4e4k4b323l4a3h463s4g453q443m4e3t2b303s2a35353l403o443v293e303k4g4n4r4g483i4g4r4j4e4o473i5n4a3t463t4o4)
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
overrideExisting: true
|
||||
schema: 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3'
|
||||
documents: 'src/**/!(*.d).{ts,tsx}'
|
||||
generates:
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"projectId": "yp82ef",
|
||||
"baseUrl": "http://localhost:3000",
|
||||
"pluginsFile": false,
|
||||
"fixturesFolder": false,
|
||||
"supportFile": "cypress/support/index.js",
|
||||
"video": false,
|
||||
"defaultCommandTimeout": 10000
|
||||
|
||||
25
cypress/fixtures/feeTierDistribution.json
Normal file
25
cypress/fixtures/feeTierDistribution.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"_meta": {
|
||||
"block": {
|
||||
"number": 99999999
|
||||
}
|
||||
},
|
||||
"asToken0": [
|
||||
{
|
||||
"feeTier": "500",
|
||||
"totalValueLockedToken0": "0",
|
||||
"totalValueLockedToken1": "1"
|
||||
},
|
||||
{
|
||||
"feeTier": "3000",
|
||||
"totalValueLockedToken0": "0",
|
||||
"totalValueLockedToken1": "7"
|
||||
},
|
||||
{
|
||||
"feeTier": "10000",
|
||||
"totalValueLockedToken0": "0",
|
||||
"totalValueLockedToken1": "2"
|
||||
}
|
||||
],
|
||||
"asToken1": []
|
||||
}
|
||||
@@ -1,4 +1,13 @@
|
||||
import { CyHttpMessages } from 'cypress/types/net-stubbing'
|
||||
import { aliasQuery, hasQuery } from '../utils/graphql-test-utils'
|
||||
|
||||
describe('Add Liquidity', () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept('POST', '/subgraphs/name/uniswap/uniswap-v3', (req) => {
|
||||
aliasQuery(req, 'feeTierDistribution')
|
||||
})
|
||||
})
|
||||
|
||||
it('loads the two correct tokens', () => {
|
||||
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/0xc778417E063141139Fce010982780140Aa0cD5Ab/500')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'MKR')
|
||||
@@ -23,4 +32,32 @@ describe('Add Liquidity', () => {
|
||||
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'MKR')
|
||||
})
|
||||
|
||||
it('loads fee tier distribution', () => {
|
||||
cy.fixture('feeTierDistribution.json').then((feeTierDistribution) => {
|
||||
cy.intercept('POST', '/subgraphs/name/uniswap/uniswap-v3', (req: CyHttpMessages.IncomingHttpRequest) => {
|
||||
if (hasQuery(req, 'feeTierDistribution')) {
|
||||
req.alias = 'feeTierDistributionQuery'
|
||||
|
||||
req.reply({
|
||||
body: {
|
||||
data: {
|
||||
...feeTierDistribution,
|
||||
},
|
||||
},
|
||||
headers: {
|
||||
'access-control-allow-origin': '*',
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/0xc778417E063141139Fce010982780140Aa0cD5Ab')
|
||||
|
||||
cy.wait('@feeTierDistributionQuery')
|
||||
|
||||
cy.get('#add-liquidity-selected-fee .selected-fee-label').should('contain.text', '0.3% fee tier')
|
||||
cy.get('#add-liquidity-selected-fee .selected-fee-percentage').should('contain.text', '70%')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
12
cypress/utils/graphql-test-utils.ts
Normal file
12
cypress/utils/graphql-test-utils.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
// Utility to match GraphQL mutation based on the query name
|
||||
export const hasQuery = (req: any, queryName: string) => {
|
||||
const { body } = req
|
||||
return body.hasOwnProperty('query') && body.query.includes(queryName)
|
||||
}
|
||||
|
||||
// Alias query if queryName matches
|
||||
export const aliasQuery = (req: any, queryName: string) => {
|
||||
if (hasQuery(req, queryName)) {
|
||||
req.alias = `${queryName}Query`
|
||||
}
|
||||
}
|
||||
20
package.json
20
package.json
@@ -19,14 +19,16 @@
|
||||
"@reach/portal": "^0.10.3",
|
||||
"@react-hook/window-scroll": "^1.3.0",
|
||||
"@reduxjs/toolkit": "^1.6.0",
|
||||
"@rtk-query/graphql-request-base-query": "^1.0.3",
|
||||
"@typechain/ethers-v5": "^7.0.0",
|
||||
"@types/d3": "^6.7.1",
|
||||
"@types/jest": "^25.2.1",
|
||||
"@types/lingui__core": "^2.7.1",
|
||||
"@types/lingui__macro": "^2.7.4",
|
||||
"@types/lingui__react": "^2.8.3",
|
||||
"@types/lodash.flatmap": "^4.5.6",
|
||||
"@types/lodash.inrange": "^3.3.6",
|
||||
"@types/luxon": "^1.24.4",
|
||||
"@types/ms.macro": "^2.0.0",
|
||||
"@types/multicodec": "^1.0.0",
|
||||
"@types/node": "^13.13.5",
|
||||
"@types/qs": "^6.9.2",
|
||||
@@ -47,13 +49,13 @@
|
||||
"@uniswap/governance": "^1.0.2",
|
||||
"@uniswap/liquidity-staker": "^1.0.2",
|
||||
"@uniswap/merkle-distributor": "1.0.1",
|
||||
"@uniswap/token-lists": "^1.0.0-beta.19",
|
||||
"@uniswap/token-lists": "^1.0.0-beta.25",
|
||||
"@uniswap/v2-core": "1.0.0",
|
||||
"@uniswap/v2-periphery": "^1.1.0-beta.0",
|
||||
"@uniswap/v2-sdk": "^3.0.0-alpha.2",
|
||||
"@uniswap/v3-core": "1.0.0",
|
||||
"@uniswap/v3-periphery": "1.0.0",
|
||||
"@uniswap/v3-sdk": "^3.0.0-alpha.9",
|
||||
"@uniswap/v3-periphery": "^1.1.1",
|
||||
"@uniswap/v3-sdk": "^3.2.1",
|
||||
"@web3-react/core": "^6.0.9",
|
||||
"@web3-react/fortmatic-connector": "^6.0.9",
|
||||
"@web3-react/injected-connector": "^6.0.7",
|
||||
@@ -64,7 +66,8 @@
|
||||
"cids": "^1.0.0",
|
||||
"copy-to-clipboard": "^3.2.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"cypress": "^4.11.0",
|
||||
"cypress": "^7.7.0",
|
||||
"d3": "^7.0.0",
|
||||
"eslint": "^7.11.0",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-plugin-prettier": "^3.1.3",
|
||||
@@ -76,7 +79,9 @@
|
||||
"inter-ui": "^3.13.1",
|
||||
"lightweight-charts": "^3.3.0",
|
||||
"lodash.flatmap": "^4.5.0",
|
||||
"lodash.inrange": "^3.3.6",
|
||||
"luxon": "^1.25.0",
|
||||
"ms.macro": "^2.0.0",
|
||||
"multicodec": "^3.0.1",
|
||||
"multihashes": "^4.0.2",
|
||||
"node-vibrant": "^3.1.5",
|
||||
@@ -89,6 +94,7 @@
|
||||
"react-dom": "^17.0.1",
|
||||
"react-feather": "^2.0.8",
|
||||
"react-ga": "^2.5.7",
|
||||
"react-is": "^17.0.2",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-popper": "^2.2.3",
|
||||
"react-redux": "^7.2.2",
|
||||
@@ -103,7 +109,7 @@
|
||||
"redux-localstorage-simple": "^2.3.1",
|
||||
"serve": "^11.3.2",
|
||||
"start-server-and-test": "^1.11.0",
|
||||
"styled-components": "^4.2.0",
|
||||
"styled-components": "^5.3.0",
|
||||
"styled-system": "^5.1.5",
|
||||
"typechain": "^5.0.0",
|
||||
"typescript": "^4.2.3",
|
||||
@@ -117,7 +123,7 @@
|
||||
"workbox-strategies": "^6.1.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"@walletconnect/web3-provider": "1.4.2-rc.2"
|
||||
"@walletconnect/web3-provider": "1.5.0-rc.5"
|
||||
},
|
||||
"scripts": {
|
||||
"compile-contract-types": "yarn compile-external-abi-types && yarn compile-v3-contract-types",
|
||||
|
||||
@@ -1,330 +0,0 @@
|
||||
[
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "callData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Multicall2.Call[]",
|
||||
"name": "calls",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"name": "aggregate",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "blockNumber",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes[]",
|
||||
"name": "returnData",
|
||||
"type": "bytes[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "callData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Multicall2.Call[]",
|
||||
"name": "calls",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"name": "blockAndAggregate",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "blockNumber",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "blockHash",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "returnData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Multicall2.Result[]",
|
||||
"name": "returnData",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "blockNumber",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "getBlockHash",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "blockHash",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "returnData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Multicall2.Result[]",
|
||||
"name": "returnData",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getBlockNumber",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "blockNumber",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getCurrentBlockCoinbase",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "coinbase",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getCurrentBlockDifficulty",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "difficulty",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getCurrentBlockGasLimit",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "gaslimit",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getCurrentBlockTimestamp",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "timestamp",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "addr",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "getEthBalance",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "balance",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getLastBlockHash",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "blockHash",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "requireSuccess",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "callData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Multicall2.Call[]",
|
||||
"name": "calls",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"name": "tryAggregate",
|
||||
"outputs": [
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "returnData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Multicall2.Result[]",
|
||||
"name": "returnData",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "requireSuccess",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "callData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Multicall2.Call[]",
|
||||
"name": "calls",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"name": "tryBlockAndAggregate",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "blockNumber",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "blockHash",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "returnData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Multicall2.Result[]",
|
||||
"name": "returnData",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
@@ -1,165 +0,0 @@
|
||||
[
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "address", "name": "target", "type": "address" },
|
||||
{ "internalType": "bytes", "name": "callData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct Multicall2.Call[]",
|
||||
"name": "calls",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"name": "aggregate",
|
||||
"outputs": [
|
||||
{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" },
|
||||
{ "internalType": "bytes[]", "name": "returnData", "type": "bytes[]" }
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "address", "name": "target", "type": "address" },
|
||||
{ "internalType": "bytes", "name": "callData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct Multicall2.Call[]",
|
||||
"name": "calls",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"name": "blockAndAggregate",
|
||||
"outputs": [
|
||||
{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" },
|
||||
{ "internalType": "bytes32", "name": "blockHash", "type": "bytes32" },
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "bool", "name": "success", "type": "bool" },
|
||||
{ "internalType": "bytes", "name": "returnData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct Multicall2.Result[]",
|
||||
"name": "returnData",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" }],
|
||||
"name": "getBlockHash",
|
||||
"outputs": [{ "internalType": "bytes32", "name": "blockHash", "type": "bytes32" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getBlockNumber",
|
||||
"outputs": [{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getCurrentBlockCoinbase",
|
||||
"outputs": [{ "internalType": "address", "name": "coinbase", "type": "address" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getCurrentBlockDifficulty",
|
||||
"outputs": [{ "internalType": "uint256", "name": "difficulty", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getCurrentBlockGasLimit",
|
||||
"outputs": [{ "internalType": "uint256", "name": "gaslimit", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getCurrentBlockTimestamp",
|
||||
"outputs": [{ "internalType": "uint256", "name": "timestamp", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [{ "internalType": "address", "name": "addr", "type": "address" }],
|
||||
"name": "getEthBalance",
|
||||
"outputs": [{ "internalType": "uint256", "name": "balance", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getLastBlockHash",
|
||||
"outputs": [{ "internalType": "bytes32", "name": "blockHash", "type": "bytes32" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "bool", "name": "requireSuccess", "type": "bool" },
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "address", "name": "target", "type": "address" },
|
||||
{ "internalType": "bytes", "name": "callData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct Multicall2.Call[]",
|
||||
"name": "calls",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"name": "tryAggregate",
|
||||
"outputs": [
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "bool", "name": "success", "type": "bool" },
|
||||
{ "internalType": "bytes", "name": "returnData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct Multicall2.Result[]",
|
||||
"name": "returnData",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "bool", "name": "requireSuccess", "type": "bool" },
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "address", "name": "target", "type": "address" },
|
||||
{ "internalType": "bytes", "name": "callData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct Multicall2.Call[]",
|
||||
"name": "calls",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"name": "tryBlockAndAggregate",
|
||||
"outputs": [
|
||||
{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" },
|
||||
{ "internalType": "bytes32", "name": "blockHash", "type": "bytes32" },
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "bool", "name": "success", "type": "bool" },
|
||||
{ "internalType": "bytes", "name": "returnData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct Multicall2.Result[]",
|
||||
"name": "returnData",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
@@ -1,471 +0,0 @@
|
||||
[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 250 KiB |
5
src/assets/svg/optimism_logo.svg
Normal file
5
src/assets/svg/optimism_logo.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="250" cy="250" r="250" fill="#FF0420"/>
|
||||
<path d="M177.133 316.446C162.247 316.446 150.051 312.943 140.544 305.938C131.162 298.808 126.471 288.676 126.471 275.541C126.471 272.789 126.784 269.411 127.409 265.408C129.036 256.402 131.35 245.581 134.352 232.947C142.858 198.547 164.812 181.347 200.213 181.347C209.845 181.347 218.476 182.973 226.107 186.225C233.738 189.352 239.742 194.106 244.12 200.486C248.498 206.74 250.688 214.246 250.688 223.002C250.688 225.629 250.375 228.944 249.749 232.947C247.873 244.08 245.621 254.901 242.994 265.408C238.616 282.546 231.048 295.368 220.29 303.874C209.532 312.255 195.147 316.446 177.133 316.446ZM179.76 289.426C186.766 289.426 192.707 287.362 197.586 283.234C202.59 279.106 206.155 272.789 208.281 264.283C211.158 252.524 213.348 242.266 214.849 233.51C215.349 230.883 215.599 228.194 215.599 225.441C215.599 214.058 209.657 208.366 197.774 208.366C190.768 208.366 184.764 210.43 179.76 214.558C174.882 218.687 171.379 225.004 169.253 233.51C167.001 241.891 164.749 252.149 162.498 264.283C161.997 266.784 161.747 269.411 161.747 272.163C161.747 283.672 167.752 289.426 179.76 289.426Z" fill="white"/>
|
||||
<path d="M259.303 314.57C257.927 314.57 256.863 314.132 256.113 313.256C255.487 312.255 255.3 311.13 255.55 309.879L281.444 187.914C281.694 186.538 282.382 185.412 283.508 184.536C284.634 183.661 285.822 183.223 287.073 183.223H336.985C350.87 183.223 362.003 186.1 370.384 191.854C378.891 197.609 383.144 205.927 383.144 216.81C383.144 219.937 382.769 223.19 382.018 226.567C378.891 240.953 372.574 251.586 363.067 258.466C353.685 265.346 340.8 268.786 324.413 268.786H299.082L290.451 309.879C290.2 311.255 289.512 312.38 288.387 313.256C287.261 314.132 286.072 314.57 284.822 314.57H259.303ZM325.727 242.892C330.98 242.892 335.546 241.453 339.424 238.576C343.427 235.699 346.054 231.571 347.305 226.192C347.68 224.065 347.868 222.189 347.868 220.563C347.868 216.935 346.805 214.183 344.678 212.307C342.551 210.305 338.924 209.305 333.795 209.305H311.278L304.148 242.892H325.727Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useContext } from 'react'
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
import { SUPPORTED_WALLETS } from '../../constants/wallet'
|
||||
import { useActiveWeb3React } from '../../hooks/web3'
|
||||
import { clearAllTransactions } from '../../state/transactions/actions'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { useContext, useCallback, ReactNode } from 'react'
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
import useENS from '../../hooks/useENS'
|
||||
import { useActiveWeb3React } from '../../hooks/web3'
|
||||
import { ExternalLink, TYPE } from '../../theme'
|
||||
|
||||
@@ -24,13 +24,6 @@ const ActiveDot = styled.span`
|
||||
margin-right: 4px;
|
||||
`
|
||||
|
||||
export const DarkBadge = styled.div`
|
||||
width: fit-content;
|
||||
border-radius: 8px;
|
||||
background-color: ${({ theme }) => theme.bg0};
|
||||
padding: 4px 6px;
|
||||
`
|
||||
|
||||
export default function RangeBadge({
|
||||
removed,
|
||||
inRange,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { readableColor } from 'polished'
|
||||
import { PropsWithChildren } from 'react'
|
||||
import styled, { DefaultTheme } from 'styled-components'
|
||||
import styled, { DefaultTheme } from 'styled-components/macro'
|
||||
import { Color } from 'theme/styled'
|
||||
|
||||
export enum BadgeVariant {
|
||||
@@ -13,7 +13,7 @@ export enum BadgeVariant {
|
||||
WARNING_OUTLINE = 'WARNING_OUTLINE',
|
||||
}
|
||||
|
||||
export interface BadgeProps {
|
||||
interface BadgeProps {
|
||||
variant?: BadgeVariant
|
||||
}
|
||||
|
||||
|
||||
@@ -12,16 +12,15 @@ const Base = styled(RebassButton)<
|
||||
{
|
||||
padding?: string
|
||||
width?: string
|
||||
borderRadius?: string
|
||||
$borderRadius?: string
|
||||
altDisabledStyle?: boolean
|
||||
} & ButtonProps
|
||||
>`
|
||||
padding: ${({ padding }) => (padding ? padding : '16px')};
|
||||
width: ${({ width }) => (width ? width : '100%')};
|
||||
padding: ${({ padding }) => padding ?? '16px'};
|
||||
width: ${({ width }) => width ?? '100%'};
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
border-radius: 20px;
|
||||
border-radius: ${({ borderRadius }) => borderRadius && borderRadius};
|
||||
border-radius: ${({ $borderRadius }) => $borderRadius ?? '20px'};
|
||||
outline: none;
|
||||
border: 1px solid transparent;
|
||||
color: white;
|
||||
@@ -42,10 +41,6 @@ const Base = styled(RebassButton)<
|
||||
transition: transform 450ms ease;
|
||||
transform: perspective(1px) translateZ(0);
|
||||
|
||||
&:hover {
|
||||
transform: scale(0.99);
|
||||
}
|
||||
|
||||
> * {
|
||||
user-select: none;
|
||||
}
|
||||
@@ -113,9 +108,7 @@ export const ButtonGray = styled(Base)`
|
||||
color: ${({ theme }) => theme.text2};
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
&:focus {
|
||||
background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.bg2)};
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.bg2)};
|
||||
}
|
||||
@@ -152,63 +145,41 @@ export const ButtonSecondary = styled(Base)`
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonPink = styled(Base)`
|
||||
background-color: ${({ theme }) => theme.primary1};
|
||||
color: white;
|
||||
|
||||
export const ButtonOutlined = styled(Base)`
|
||||
border: 1px solid ${({ theme }) => theme.bg2};
|
||||
background-color: transparent;
|
||||
color: ${({ theme }) => theme.text1};
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.primary1)};
|
||||
background-color: ${({ theme }) => darken(0.05, theme.primary1)};
|
||||
box-shadow: 0 0 0 1px ${({ theme }) => theme.bg4};
|
||||
}
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => darken(0.05, theme.primary1)};
|
||||
box-shadow: 0 0 0 1px ${({ theme }) => theme.bg4};
|
||||
}
|
||||
&:active {
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, theme.primary1)};
|
||||
background-color: ${({ theme }) => darken(0.1, theme.primary1)};
|
||||
box-shadow: 0 0 0 1px ${({ theme }) => theme.bg4};
|
||||
}
|
||||
&:disabled {
|
||||
background-color: ${({ theme }) => theme.primary1};
|
||||
opacity: 50%;
|
||||
cursor: auto;
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonUNIGradient = styled(ButtonPrimary)`
|
||||
export const ButtonYellow = styled(Base)`
|
||||
background-color: ${({ theme }) => theme.yellow3};
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
height: 36px;
|
||||
font-weight: 500;
|
||||
background-color: ${({ theme }) => theme.bg3};
|
||||
background: radial-gradient(174.47% 188.91% at 1.84% 0%, #ff007a 0%, #2172e5 100%), #edeef2;
|
||||
width: fit-content;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
white-space: no-wrap;
|
||||
:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
:active {
|
||||
opacity: 0.9;
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonOutlined = styled(Base)`
|
||||
border: 1px solid ${({ theme }) => theme.bg2};
|
||||
background-color: transparent;
|
||||
color: ${({ theme }) => theme.text1};
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 1px ${({ theme }) => theme.bg4};
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.yellow3)};
|
||||
background-color: ${({ theme }) => darken(0.05, theme.yellow3)};
|
||||
}
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 1px ${({ theme }) => theme.bg4};
|
||||
background-color: ${({ theme }) => darken(0.05, theme.yellow3)};
|
||||
}
|
||||
&:active {
|
||||
box-shadow: 0 0 0 1px ${({ theme }) => theme.bg4};
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, theme.yellow3)};
|
||||
background-color: ${({ theme }) => darken(0.1, theme.yellow3)};
|
||||
}
|
||||
&:disabled {
|
||||
background-color: ${({ theme }) => theme.yellow3};
|
||||
opacity: 50%;
|
||||
cursor: auto;
|
||||
}
|
||||
@@ -258,27 +229,6 @@ export const ButtonText = styled(Base)`
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonWhite = styled(Base)`
|
||||
border: 1px solid #edeef2;
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
color: black;
|
||||
|
||||
&:focus {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
box-shadow: 0 0 0 1pt ${darken(0.05, '#edeef2')};
|
||||
}
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 1pt ${darken(0.1, '#edeef2')};
|
||||
}
|
||||
&:active {
|
||||
box-shadow: 0 0 0 1pt ${darken(0.1, '#edeef2')};
|
||||
}
|
||||
&:disabled {
|
||||
opacity: 50%;
|
||||
cursor: auto;
|
||||
}
|
||||
`
|
||||
|
||||
const ButtonConfirmedStyle = styled(Base)`
|
||||
background-color: ${({ theme }) => theme.bg3};
|
||||
color: ${({ theme }) => theme.text1};
|
||||
@@ -347,17 +297,6 @@ export function ButtonDropdown({ disabled = false, children, ...rest }: { disabl
|
||||
)
|
||||
}
|
||||
|
||||
export function ButtonDropdownGrey({ disabled = false, children, ...rest }: { disabled?: boolean } & ButtonProps) {
|
||||
return (
|
||||
<ButtonGray {...rest} disabled={disabled} style={{ borderRadius: '20px' }}>
|
||||
<RowBetween>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>{children}</div>
|
||||
<ChevronDown size={24} />
|
||||
</RowBetween>
|
||||
</ButtonGray>
|
||||
)
|
||||
}
|
||||
|
||||
export function ButtonDropdownLight({ disabled = false, children, ...rest }: { disabled?: boolean } & ButtonProps) {
|
||||
return (
|
||||
<ButtonOutlined {...rest} disabled={disabled}>
|
||||
@@ -369,14 +308,6 @@ export function ButtonDropdownLight({ disabled = false, children, ...rest }: { d
|
||||
)
|
||||
}
|
||||
|
||||
export function ButtonRadio({ active, ...rest }: { active?: boolean } & ButtonProps) {
|
||||
if (!active) {
|
||||
return <ButtonWhite {...rest} />
|
||||
} else {
|
||||
return <ButtonPrimary {...rest} />
|
||||
}
|
||||
}
|
||||
|
||||
const ActiveOutlined = styled(ButtonOutlined)`
|
||||
border: 1px solid;
|
||||
border-color: ${({ theme }) => theme.primary1};
|
||||
@@ -409,13 +340,13 @@ export function ButtonRadioChecked({ active = false, children, ...rest }: { acti
|
||||
|
||||
if (!active) {
|
||||
return (
|
||||
<ButtonOutlined borderRadius="12px" padding="12px 8px" {...rest}>
|
||||
<ButtonOutlined $borderRadius="12px" padding="12px 8px" {...rest}>
|
||||
{<RowBetween>{children}</RowBetween>}
|
||||
</ButtonOutlined>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<ActiveOutlined {...rest} padding="12px 8px" borderRadius="12px">
|
||||
<ActiveOutlined {...rest} padding="12px 8px" $borderRadius="12px">
|
||||
{
|
||||
<RowBetween>
|
||||
{children}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import styled from 'styled-components/macro'
|
||||
import { Box } from 'rebass/styled-components'
|
||||
|
||||
const Card = styled(Box)<{ width?: string; padding?: string; border?: string; borderRadius?: string }>`
|
||||
const Card = styled(Box)<{ width?: string; padding?: string; border?: string; $borderRadius?: string }>`
|
||||
width: ${({ width }) => width ?? '100%'};
|
||||
border-radius: 16px;
|
||||
padding: 1rem;
|
||||
padding: ${({ padding }) => padding};
|
||||
padding: ${({ padding }) => padding ?? '1rem'};
|
||||
border-radius: ${({ $borderRadius }) => $borderRadius ?? '16px'};
|
||||
border: ${({ border }) => border};
|
||||
border-radius: ${({ borderRadius }) => borderRadius};
|
||||
`
|
||||
export default Card
|
||||
|
||||
@@ -42,12 +40,6 @@ export const YellowCard = styled(Card)`
|
||||
font-weight: 500;
|
||||
`
|
||||
|
||||
export const PinkCard = styled(Card)`
|
||||
background-color: rgba(255, 0, 122, 0.03);
|
||||
color: ${({ theme }) => theme.primary1};
|
||||
font-weight: 500;
|
||||
`
|
||||
|
||||
export const BlueCard = styled(Card)`
|
||||
background-color: ${({ theme }) => theme.primary5};
|
||||
color: ${({ theme }) => theme.blue2};
|
||||
|
||||
@@ -9,7 +9,7 @@ const Wrapper = styled.div<{ margin: boolean; sizeraw: number }>`
|
||||
margin-left: ${({ sizeraw, margin }) => margin && (sizeraw / 3 + 8).toString() + 'px'};
|
||||
`
|
||||
|
||||
export interface DoubleCurrencyLogoProps {
|
||||
interface DoubleCurrencyLogoProps {
|
||||
margin?: boolean
|
||||
size?: number
|
||||
currency0?: Currency
|
||||
|
||||
@@ -1,80 +1,228 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { DynamicSection } from 'pages/AddLiquidity/styled'
|
||||
import { TYPE } from 'theme'
|
||||
import { RowBetween } from 'components/Row'
|
||||
import { ButtonRadioChecked } from 'components/Button'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ButtonGray, ButtonRadioChecked } from 'components/Button'
|
||||
import styled, { keyframes } from 'styled-components/macro'
|
||||
import Badge from 'components/Badge'
|
||||
import Card from 'components/Card'
|
||||
import usePrevious from 'hooks/usePrevious'
|
||||
import { useFeeTierDistribution } from 'hooks/useFeeTierDistribution'
|
||||
import ReactGA from 'react-ga'
|
||||
import { Box } from 'rebass'
|
||||
|
||||
const pulse = (color: string) => keyframes`
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 ${color};
|
||||
}
|
||||
|
||||
70% {
|
||||
box-shadow: 0 0 0 2px ${color};
|
||||
}
|
||||
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 ${color};
|
||||
}
|
||||
`
|
||||
|
||||
const ResponsiveText = styled(TYPE.label)`
|
||||
line-height: 16px;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
`};
|
||||
`
|
||||
|
||||
const FocusedOutlineCard = styled(Card)<{ pulsing: boolean }>`
|
||||
border: 1px solid ${({ theme }) => theme.bg2};
|
||||
animation: ${({ pulsing, theme }) => pulsing && pulse(theme.primary1)} 0.6s linear;
|
||||
`
|
||||
|
||||
const FeeAmountLabel = {
|
||||
[FeeAmount.LOW]: {
|
||||
label: '0.05',
|
||||
description: <Trans>Best for stable pairs.</Trans>,
|
||||
},
|
||||
[FeeAmount.MEDIUM]: {
|
||||
label: '0.3',
|
||||
description: <Trans>Best for most pairs.</Trans>,
|
||||
},
|
||||
[FeeAmount.HIGH]: {
|
||||
label: '1',
|
||||
description: <Trans>Best for exotic pairs.</Trans>,
|
||||
},
|
||||
}
|
||||
|
||||
const FeeTierPercentageBadge = ({ percentage }: { percentage: number | undefined }) => {
|
||||
return (
|
||||
<Badge>
|
||||
<TYPE.label fontSize={12}>
|
||||
{percentage !== undefined ? <Trans>{percentage?.toFixed(0)}% select</Trans> : <Trans>Not created</Trans>}
|
||||
</TYPE.label>
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
|
||||
export default function FeeSelector({
|
||||
disabled = false,
|
||||
feeAmount,
|
||||
handleFeePoolSelect,
|
||||
currencyA,
|
||||
currencyB,
|
||||
}: {
|
||||
disabled?: boolean
|
||||
feeAmount?: FeeAmount
|
||||
handleFeePoolSelect: (feeAmount: FeeAmount) => void
|
||||
currencyA?: Currency | undefined
|
||||
currencyB?: Currency | undefined
|
||||
}) {
|
||||
const { isLoading, isError, largestUsageFeeTier, distributions } = useFeeTierDistribution(currencyA, currencyB)
|
||||
|
||||
const [showOptions, setShowOptions] = useState(false)
|
||||
const [pulsing, setPulsing] = useState(false)
|
||||
|
||||
const previousFeeAmount = usePrevious(feeAmount)
|
||||
|
||||
const recommended = useRef(false)
|
||||
|
||||
const handleFeePoolSelectWithEvent = useCallback(
|
||||
(fee) => {
|
||||
ReactGA.event({
|
||||
category: 'FeePoolSelect',
|
||||
action: 'Manual',
|
||||
})
|
||||
handleFeePoolSelect(fee)
|
||||
},
|
||||
[handleFeePoolSelect]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (feeAmount || isLoading || isError) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!largestUsageFeeTier) {
|
||||
// cannot recommend, open options
|
||||
setShowOptions(true)
|
||||
} else {
|
||||
setShowOptions(false)
|
||||
|
||||
recommended.current = true
|
||||
ReactGA.event({
|
||||
category: 'FeePoolSelect',
|
||||
action: ' Recommended',
|
||||
})
|
||||
|
||||
handleFeePoolSelect(largestUsageFeeTier)
|
||||
}
|
||||
}, [feeAmount, isLoading, isError, largestUsageFeeTier, handleFeePoolSelect])
|
||||
|
||||
useEffect(() => {
|
||||
setShowOptions(isError)
|
||||
}, [isError])
|
||||
|
||||
useEffect(() => {
|
||||
if (feeAmount && previousFeeAmount !== feeAmount) {
|
||||
setPulsing(true)
|
||||
}
|
||||
}, [previousFeeAmount, feeAmount])
|
||||
|
||||
return (
|
||||
<AutoColumn gap="16px">
|
||||
<DynamicSection gap="md" disabled={disabled}>
|
||||
<TYPE.label>
|
||||
<Trans>Select Pool</Trans>
|
||||
</TYPE.label>
|
||||
<TYPE.main fontSize={14} fontWeight={400} style={{ marginBottom: '.5rem', lineHeight: '125%' }}>
|
||||
<Trans>Select a pool type based on your preferred liquidity provider fee.</Trans>
|
||||
</TYPE.main>
|
||||
<RowBetween>
|
||||
<ButtonRadioChecked
|
||||
width="32%"
|
||||
active={feeAmount === FeeAmount.LOW}
|
||||
onClick={() => handleFeePoolSelect(FeeAmount.LOW)}
|
||||
>
|
||||
<AutoColumn gap="sm" justify="flex-start">
|
||||
<ResponsiveText>
|
||||
<Trans>0.05% fee</Trans>
|
||||
</ResponsiveText>
|
||||
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
|
||||
<Trans>Best for stable pairs.</Trans>
|
||||
</TYPE.main>
|
||||
<FocusedOutlineCard pulsing={pulsing} onAnimationEnd={() => setPulsing(false)}>
|
||||
<RowBetween>
|
||||
<AutoColumn id="add-liquidity-selected-fee">
|
||||
{!feeAmount ? (
|
||||
<>
|
||||
<TYPE.label>
|
||||
<Trans>Fee tier</Trans>
|
||||
</TYPE.label>
|
||||
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
|
||||
<Trans>The % you will earn in fees.</Trans>
|
||||
</TYPE.main>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<TYPE.label className="selected-fee-label">
|
||||
<Trans>{FeeAmountLabel[feeAmount].label}% fee tier</Trans>
|
||||
</TYPE.label>
|
||||
<Box style={{ width: 'fit-content', marginTop: '8px' }} className="selected-fee-percentage">
|
||||
{distributions && feeAmount && <FeeTierPercentageBadge percentage={distributions[feeAmount]} />}
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</AutoColumn>
|
||||
</ButtonRadioChecked>
|
||||
<ButtonRadioChecked
|
||||
width="32%"
|
||||
active={feeAmount === FeeAmount.MEDIUM}
|
||||
onClick={() => handleFeePoolSelect(FeeAmount.MEDIUM)}
|
||||
>
|
||||
<AutoColumn gap="sm" justify="flex-start">
|
||||
<ResponsiveText>
|
||||
<Trans>0.3% fee</Trans>
|
||||
</ResponsiveText>
|
||||
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
|
||||
<Trans>Best for most pairs.</Trans>
|
||||
</TYPE.main>
|
||||
</AutoColumn>
|
||||
</ButtonRadioChecked>
|
||||
<ButtonRadioChecked
|
||||
width="32%"
|
||||
active={feeAmount === FeeAmount.HIGH}
|
||||
onClick={() => handleFeePoolSelect(FeeAmount.HIGH)}
|
||||
>
|
||||
<AutoColumn gap="sm" justify="flex-start">
|
||||
<ResponsiveText>
|
||||
<Trans>1% fee</Trans>
|
||||
</ResponsiveText>
|
||||
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
|
||||
<Trans>Best for exotic pairs.</Trans>
|
||||
</TYPE.main>
|
||||
</AutoColumn>
|
||||
</ButtonRadioChecked>
|
||||
</RowBetween>
|
||||
|
||||
<ButtonGray onClick={() => setShowOptions(!showOptions)} width="auto" padding="4px" $borderRadius="6px">
|
||||
{showOptions ? <Trans>Hide</Trans> : <Trans>Edit</Trans>}
|
||||
</ButtonGray>
|
||||
</RowBetween>
|
||||
</FocusedOutlineCard>
|
||||
|
||||
{showOptions && (
|
||||
<RowBetween>
|
||||
<ButtonRadioChecked
|
||||
width="32%"
|
||||
active={feeAmount === FeeAmount.LOW}
|
||||
onClick={() => handleFeePoolSelectWithEvent(FeeAmount.LOW)}
|
||||
>
|
||||
<AutoColumn gap="sm" justify="flex-start">
|
||||
<AutoColumn justify="flex-start" gap="6px">
|
||||
<ResponsiveText>
|
||||
<Trans>0.05% fee</Trans>
|
||||
</ResponsiveText>
|
||||
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
|
||||
<Trans>Best for stable pairs.</Trans>
|
||||
</TYPE.main>
|
||||
</AutoColumn>
|
||||
|
||||
{distributions && <FeeTierPercentageBadge percentage={distributions[FeeAmount.LOW]} />}
|
||||
</AutoColumn>
|
||||
</ButtonRadioChecked>
|
||||
<ButtonRadioChecked
|
||||
width="32%"
|
||||
active={feeAmount === FeeAmount.MEDIUM}
|
||||
onClick={() => handleFeePoolSelectWithEvent(FeeAmount.MEDIUM)}
|
||||
>
|
||||
<AutoColumn gap="sm" justify="flex-start">
|
||||
<AutoColumn justify="flex-start" gap="4px">
|
||||
<ResponsiveText>
|
||||
<Trans>0.3% fee</Trans>
|
||||
</ResponsiveText>
|
||||
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
|
||||
<Trans>Best for most pairs.</Trans>
|
||||
</TYPE.main>
|
||||
</AutoColumn>
|
||||
|
||||
{distributions && <FeeTierPercentageBadge percentage={distributions[FeeAmount.MEDIUM]} />}
|
||||
</AutoColumn>
|
||||
</ButtonRadioChecked>
|
||||
<ButtonRadioChecked
|
||||
width="32%"
|
||||
active={feeAmount === FeeAmount.HIGH}
|
||||
onClick={() => handleFeePoolSelectWithEvent(FeeAmount.HIGH)}
|
||||
>
|
||||
<AutoColumn gap="sm" justify="flex-start">
|
||||
<AutoColumn justify="flex-start" gap="4px">
|
||||
<ResponsiveText>
|
||||
<Trans>1% fee</Trans>
|
||||
</ResponsiveText>
|
||||
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
|
||||
<Trans>Best for exotic pairs.</Trans>
|
||||
</TYPE.main>
|
||||
</AutoColumn>
|
||||
|
||||
{distributions && <FeeTierPercentageBadge percentage={distributions[FeeAmount.HIGH]} />}
|
||||
</AutoColumn>
|
||||
</ButtonRadioChecked>
|
||||
</RowBetween>
|
||||
)}
|
||||
</DynamicSection>
|
||||
</AutoColumn>
|
||||
)
|
||||
|
||||
@@ -1,47 +1,45 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import arbitrumLogoUrl from 'assets/svg/arbitrum_logo.svg'
|
||||
import { YellowCard } from 'components/Card'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import { useActiveWeb3React } from 'hooks/web3'
|
||||
import { transparentize } from 'polished'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { ArrowDownCircle } from 'react-feather'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { ArrowDownCircle, ChevronDown, ToggleLeft } from 'react-feather'
|
||||
import { ApplicationModal } from 'state/application/actions'
|
||||
import { useModalOpen, useToggleModal } from 'state/application/hooks'
|
||||
import styled, { css } from 'styled-components'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
import { ExternalLink } from 'theme'
|
||||
import { switchToNetwork } from 'utils/switchToNetwork'
|
||||
import { NETWORK_LABELS, SupportedChainId } from '../../constants/chains'
|
||||
import { CHAIN_INFO, L2_CHAIN_IDS, NETWORK_LABELS, SupportedChainId, SupportedL2ChainId } from '../../constants/chains'
|
||||
|
||||
const BaseWrapper = css`
|
||||
position: relative;
|
||||
margin-right: 8px;
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
margin-left: 12px;
|
||||
justify-self: end;
|
||||
`};
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
margin: 0 0.5rem 0 0;
|
||||
width: initial;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex-shrink: 1;
|
||||
`};
|
||||
`
|
||||
const ArbitrumWrapper = styled.div`
|
||||
const L2Wrapper = styled.div`
|
||||
${BaseWrapper}
|
||||
`
|
||||
const BaseMenuItem = css`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => transparentize(0.9, theme.primary1)};
|
||||
background-color: transparent;
|
||||
border-radius: 12px;
|
||||
color: ${({ theme }) => theme.text2};
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
font-size: 14px;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
justify-content: space-between;
|
||||
padding: 12px;
|
||||
:hover {
|
||||
color: ${({ theme }) => theme.text1};
|
||||
text-decoration: none;
|
||||
@@ -64,30 +62,29 @@ const DisabledMenuItem = styled.div`
|
||||
`
|
||||
const FallbackWrapper = styled(YellowCard)`
|
||||
${BaseWrapper}
|
||||
width: auto;
|
||||
border-radius: 12px;
|
||||
padding: 8px 12px;
|
||||
width: 100%;
|
||||
`
|
||||
const Icon = styled.img`
|
||||
width: 17px;
|
||||
`
|
||||
const L1Tag = styled.div`
|
||||
color: #c4d9f8;
|
||||
opacity: 40%;
|
||||
`
|
||||
const L2Tag = styled.div`
|
||||
background-color: ${({ theme }) => theme.primary1};
|
||||
border-radius: 6px;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
padding: 2px 6px;
|
||||
width: 16px;
|
||||
margin-right: 2px;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
margin-right: 4px;
|
||||
|
||||
`};
|
||||
`
|
||||
|
||||
const MenuFlyout = styled.span`
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
border: 1px solid ${({ theme }) => theme.bg0};
|
||||
|
||||
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-radius: 20px;
|
||||
padding: 8px;
|
||||
border-radius: 12px;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 1rem;
|
||||
@@ -97,22 +94,27 @@ const MenuFlyout = styled.span`
|
||||
z-index: 100;
|
||||
width: 237px;
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
top: -14.25rem;
|
||||
|
||||
bottom: unset;
|
||||
top: 4.5em
|
||||
right: 0;
|
||||
|
||||
`};
|
||||
> {
|
||||
padding: 12px;
|
||||
}
|
||||
> :not(:first-child) {
|
||||
margin-top: 4px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
> :not(:last-child) {
|
||||
margin-bottom: 4px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
`
|
||||
const LinkOutCircle = styled(ArrowDownCircle)`
|
||||
transform: rotate(230deg);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
opacity: 0.6;
|
||||
`
|
||||
const MenuItem = styled(ExternalLink)`
|
||||
${BaseMenuItem}
|
||||
@@ -120,31 +122,45 @@ const MenuItem = styled(ExternalLink)`
|
||||
const ButtonMenuItem = styled.button`
|
||||
${BaseMenuItem}
|
||||
border: none;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
color: ${({ theme }) => theme.text2};
|
||||
outline: none;
|
||||
padding: 0;
|
||||
`
|
||||
const NetworkInfo = styled.button`
|
||||
const NetworkInfo = styled.button<{ chainId: SupportedChainId }>`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
background-color: ${({ theme }) => theme.bg0};
|
||||
border-radius: 12px;
|
||||
border: 1px solid ${({ theme }) => theme.bg0};
|
||||
color: ${({ theme }) => theme.text1};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
height: 100%;
|
||||
justify-content: space-between;
|
||||
margin: 0;
|
||||
padding: 8px;
|
||||
width: 172px;
|
||||
height: 38px;
|
||||
padding: 0.7rem;
|
||||
|
||||
:hover,
|
||||
:focus {
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
background-color: ${({ theme }) => theme.bg3};
|
||||
border: 1px solid ${({ theme }) => theme.bg3};
|
||||
}
|
||||
`
|
||||
const NetworkName = styled.div<{ chainId: SupportedChainId }>`
|
||||
border-radius: 6px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
padding: 0 2px 0.5px 4px;
|
||||
margin: 0 2px;
|
||||
white-space: pre;
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
display: none;
|
||||
`};
|
||||
`
|
||||
|
||||
export default function NetworkCard() {
|
||||
const { chainId, library } = useActiveWeb3React()
|
||||
const node = useRef<HTMLDivElement>(null)
|
||||
@@ -169,29 +185,27 @@ export default function NetworkCard() {
|
||||
return null
|
||||
}
|
||||
|
||||
if (chainId === SupportedChainId.ARBITRUM_ONE) {
|
||||
if (L2_CHAIN_IDS.includes(chainId)) {
|
||||
const info = CHAIN_INFO[chainId as SupportedL2ChainId]
|
||||
const isArbitrum = [SupportedChainId.ARBITRUM_ONE, SupportedChainId.ARBITRUM_RINKEBY].includes(chainId)
|
||||
return (
|
||||
<ArbitrumWrapper ref={node}>
|
||||
<NetworkInfo onClick={toggle}>
|
||||
<Icon src={arbitrumLogoUrl} />
|
||||
<span>Arbitrum</span>
|
||||
<L2Tag>L2 Alpha</L2Tag>
|
||||
<L2Wrapper ref={node}>
|
||||
<NetworkInfo onClick={toggle} chainId={chainId}>
|
||||
<Icon src={info.logoUrl} />
|
||||
<NetworkName chainId={chainId}>{info.label}</NetworkName>
|
||||
<ChevronDown size={16} style={{ marginTop: '2px' }} strokeWidth={2.5} />
|
||||
</NetworkInfo>
|
||||
{open && (
|
||||
<MenuFlyout>
|
||||
<MenuItem href="https://bridge.arbitrum.io/">
|
||||
<div>
|
||||
<Trans>Arbitrum Token Bridge</Trans>
|
||||
</div>
|
||||
<MenuItem href={info.bridge}>
|
||||
<div>{isArbitrum ? <Trans>{info.label} Bridge</Trans> : <Trans>Optimistic L2 Gateway</Trans>}</div>
|
||||
<LinkOutCircle />
|
||||
</MenuItem>
|
||||
<MenuItem href="https://explorer.arbitrum.io/">
|
||||
<div>
|
||||
<Trans>Arbitrum Explorer</Trans>
|
||||
</div>
|
||||
<MenuItem href={info.explorer}>
|
||||
{isArbitrum ? <Trans>{info.label} Explorer</Trans> : <Trans>Optimistic Etherscan</Trans>}
|
||||
<LinkOutCircle />
|
||||
</MenuItem>
|
||||
<MenuItem href="https://offchainlabs.com/">
|
||||
<MenuItem href={info.docs}>
|
||||
<div>
|
||||
<Trans>Learn more</Trans>
|
||||
</div>
|
||||
@@ -200,9 +214,9 @@ export default function NetworkCard() {
|
||||
{implements3085 ? (
|
||||
<ButtonMenuItem onClick={() => switchToNetwork({ library, chainId: SupportedChainId.MAINNET })}>
|
||||
<div>
|
||||
<Trans>Switch to Ethereum</Trans>
|
||||
<Trans>Switch to L1 (Mainnet)</Trans>
|
||||
</div>
|
||||
<L1Tag>L1</L1Tag>
|
||||
<ToggleLeft opacity={0.6} size={16} />
|
||||
</ButtonMenuItem>
|
||||
) : (
|
||||
<DisabledMenuItem>
|
||||
@@ -211,7 +225,7 @@ export default function NetworkCard() {
|
||||
)}
|
||||
</MenuFlyout>
|
||||
)}
|
||||
</ArbitrumWrapper>
|
||||
</L2Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import styled, { keyframes } from 'styled-components'
|
||||
import styled, { keyframes } from 'styled-components/macro'
|
||||
import { useActiveWeb3React } from '../../hooks/web3'
|
||||
|
||||
import { useBlockNumber } from '../../state/application/hooks'
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { AlertTriangle, X } from 'react-feather'
|
||||
import { useURLWarningToggle, useURLWarningVisible } from '../../state/user/hooks'
|
||||
import { isMobile } from 'react-device-detect'
|
||||
import { Trans } from '@lingui/macro'
|
||||
|
||||
const PhishAlert = styled.div<{ isActive: any }>`
|
||||
width: 100%;
|
||||
padding: 6px 6px;
|
||||
background-color: ${({ theme }) => theme.blue1};
|
||||
color: white;
|
||||
font-size: 11px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
display: ${({ isActive }) => (isActive ? 'flex' : 'none')};
|
||||
`
|
||||
|
||||
export const StyledClose = styled(X)`
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
|
||||
export default function URLWarning() {
|
||||
const toggleURLWarning = useURLWarningToggle()
|
||||
const showURLWarning = useURLWarningVisible()
|
||||
|
||||
return isMobile ? (
|
||||
<PhishAlert isActive={showURLWarning}>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<AlertTriangle style={{ marginRight: 6 }} size={12} />
|
||||
<Trans>
|
||||
Make sure the URL is
|
||||
<code style={{ padding: '0 4px', display: 'inline', fontWeight: 'bold' }}>app.uniswap.org</code>
|
||||
</Trans>
|
||||
</div>
|
||||
<StyledClose size={12} onClick={toggleURLWarning} />
|
||||
</PhishAlert>
|
||||
) : window.location.hostname === 'app.uniswap.org' ? (
|
||||
<PhishAlert isActive={showURLWarning}>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<AlertTriangle style={{ marginRight: 6 }} size={12} />
|
||||
<Trans>
|
||||
Always make sure the URL is
|
||||
<code style={{ padding: '0 4px', display: 'inline', fontWeight: 'bold' }}>app.uniswap.org</code> - bookmark it
|
||||
to be safe.
|
||||
</Trans>
|
||||
</div>
|
||||
<StyledClose size={12} onClick={toggleURLWarning} />
|
||||
</PhishAlert>
|
||||
) : null
|
||||
}
|
||||
@@ -1,22 +1,23 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { CHAIN_INFO, SupportedChainId } from 'constants/chains'
|
||||
import { useMemo } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import tokenLogo from '../../assets/images/token-logo.png'
|
||||
import { UNI } from '../../constants/tokens'
|
||||
import { useTotalSupply } from '../../hooks/useTotalSupply'
|
||||
import { useActiveWeb3React } from '../../hooks/web3'
|
||||
import { useMerkleDistributorContract } from '../../hooks/useContract'
|
||||
import useCurrentBlockTimestamp from '../../hooks/useCurrentBlockTimestamp'
|
||||
import { useTotalSupply } from '../../hooks/useTotalSupply'
|
||||
import useUSDCPrice from '../../hooks/useUSDCPrice'
|
||||
import { useActiveWeb3React } from '../../hooks/web3'
|
||||
import { useTotalUniEarned } from '../../state/stake/hooks'
|
||||
import { useAggregateUniBalance, useTokenBalance } from '../../state/wallet/hooks'
|
||||
import { ExternalLink, StyledInternalLink, TYPE, UniTokenAnimated } from '../../theme'
|
||||
import { computeUniCirculation } from '../../utils/computeUniCirculation'
|
||||
import useUSDCPrice from '../../hooks/useUSDCPrice'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { RowBetween } from '../Row'
|
||||
import { Break, CardBGImage, CardNoise, CardSection, DataCard } from '../earn/styled'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { RowBetween } from '../Row'
|
||||
|
||||
const ContentWrapper = styled(AutoColumn)`
|
||||
width: 100%;
|
||||
@@ -59,6 +60,8 @@ export default function UniBalanceContent({ setShowUniBalanceModal }: { setShowU
|
||||
[blockTimestamp, chainId, totalSupply, unclaimedUni, uni]
|
||||
)
|
||||
|
||||
const { infoLink } = CHAIN_INFO[chainId ? chainId : SupportedChainId.MAINNET]
|
||||
|
||||
return (
|
||||
<ContentWrapper gap="lg">
|
||||
<ModalUpper>
|
||||
@@ -128,7 +131,7 @@ export default function UniBalanceContent({ setShowUniBalanceModal }: { setShowU
|
||||
<TYPE.white color="white">{totalSupply?.toFixed(0, { groupSeparator: ',' })}</TYPE.white>
|
||||
</RowBetween>
|
||||
{uni && uni.chainId === 1 ? (
|
||||
<ExternalLink href={`https://info.uniswap.org/token/${uni.address}`}>
|
||||
<ExternalLink href={`${infoLink}/token/${uni.address}`}>
|
||||
<Trans>View UNI Analytics</Trans>
|
||||
</ExternalLink>
|
||||
) : null}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import useScrollPosition from '@react-hook/window-scroll'
|
||||
import { CHAIN_INFO, SupportedChainId } from 'constants/chains'
|
||||
import { darken } from 'polished'
|
||||
import { useState } from 'react'
|
||||
import { Moon, Sun } from 'react-feather'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { Text } from 'rebass'
|
||||
import { useShowClaimPopup, useToggleSelfClaimModal } from 'state/application/hooks'
|
||||
@@ -19,7 +19,7 @@ import ClaimModal from '../claim/ClaimModal'
|
||||
import { CardNoise } from '../earn/styled'
|
||||
import Menu from '../Menu'
|
||||
import Modal from '../Modal'
|
||||
import Row, { RowFixed } from '../Row'
|
||||
import Row from '../Row'
|
||||
import { Dots } from '../swap/styleds'
|
||||
import Web3Status from '../Web3Status'
|
||||
import NetworkCard from './NetworkCard'
|
||||
@@ -38,22 +38,27 @@ const HeaderFrame = styled.div<{ showBackground: boolean }>`
|
||||
padding: 1rem;
|
||||
z-index: 21;
|
||||
position: relative;
|
||||
|
||||
/* Background slide effect on scroll. */
|
||||
background-image: ${({ theme }) => `linear-gradient(to bottom, transparent 50%, ${theme.bg0} 50% )}}`}
|
||||
background-image: ${({ theme }) => `linear-gradient(to bottom, transparent 50%, ${theme.bg0} 50% )}}`};
|
||||
background-position: ${({ showBackground }) => (showBackground ? '0 -100%' : '0 0')};
|
||||
background-size: 100% 200%;
|
||||
box-shadow: 0px 0px 0px 1px ${({ theme, showBackground }) => (showBackground ? theme.bg2 : 'transparent;')};
|
||||
transition: background-position .1s, box-shadow .1s;
|
||||
transition: background-position 0.1s, box-shadow 0.1s;
|
||||
background-blend-mode: hard-light;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToLarge`
|
||||
grid-template-columns: 48px 1fr 1fr;
|
||||
`};
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
padding: 1rem;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
`};
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToExtraSmall`
|
||||
padding: 1rem;
|
||||
`}
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
padding: 1rem;
|
||||
grid-template-columns: 36px 1fr;
|
||||
`};
|
||||
`
|
||||
|
||||
const HeaderControls = styled.div`
|
||||
@@ -61,23 +66,6 @@ const HeaderControls = styled.div`
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-self: flex-end;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
justify-self: center;
|
||||
width: 100%;
|
||||
max-width: 960px;
|
||||
padding: 1rem;
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
z-index: 99;
|
||||
height: 72px;
|
||||
border-radius: 12px 12px 0 0;
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
`};
|
||||
`
|
||||
|
||||
const HeaderElement = styled.div`
|
||||
@@ -90,22 +78,10 @@ const HeaderElement = styled.div`
|
||||
}
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
flex-direction: row-reverse;
|
||||
align-items: center;
|
||||
`};
|
||||
`
|
||||
|
||||
const HeaderElementWrap = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const HeaderRow = styled(RowFixed)`
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
width: 100%;
|
||||
`};
|
||||
`
|
||||
|
||||
const HeaderLinks = styled(Row)`
|
||||
justify-self: center;
|
||||
background-color: ${({ theme }) => theme.bg0};
|
||||
@@ -116,8 +92,25 @@ const HeaderLinks = styled(Row)`
|
||||
grid-auto-flow: column;
|
||||
grid-gap: 10px;
|
||||
overflow: auto;
|
||||
align-items: center;
|
||||
${({ theme }) => theme.mediaWidth.upToLarge`
|
||||
justify-self: start;
|
||||
`};
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
justify-self: flex-end;
|
||||
justify-self: center;
|
||||
`};
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
justify-self: center;
|
||||
z-index: 99;
|
||||
position: fixed;
|
||||
bottom: 0; right: 50%;
|
||||
transform: translate(50%,-50%);
|
||||
margin: 0 auto;
|
||||
background-color: ${({ theme }) => theme.bg0};
|
||||
border: 1px solid ${({ theme }) => theme.bg2};
|
||||
box-shadow: 0px 6px 10px rgb(0 0 0 / 2%);
|
||||
`};
|
||||
`
|
||||
|
||||
@@ -125,7 +118,7 @@ const AccountElement = styled.div<{ active: boolean }>`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
background-color: ${({ theme, active }) => (!active ? theme.bg1 : theme.bg2)};
|
||||
background-color: ${({ theme, active }) => (!active ? theme.bg1 : theme.bg1)};
|
||||
border-radius: 12px;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
@@ -159,12 +152,6 @@ const UNIWrapper = styled.span`
|
||||
}
|
||||
`
|
||||
|
||||
const HideSmall = styled.span`
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
display: none;
|
||||
`};
|
||||
`
|
||||
|
||||
const BalanceText = styled(Text)`
|
||||
${({ theme }) => theme.mediaWidth.upToExtraSmall`
|
||||
display: none;
|
||||
@@ -249,47 +236,13 @@ const StyledExternalLink = styled(ExternalLink).attrs({
|
||||
color: ${({ theme }) => darken(0.1, theme.text1)};
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToExtraSmall`
|
||||
display: none;
|
||||
`}
|
||||
`
|
||||
|
||||
export const StyledMenuButton = styled.button`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 35px;
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
margin-left: 8px;
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
:hover,
|
||||
:focus {
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
background-color: ${({ theme }) => theme.bg4};
|
||||
}
|
||||
|
||||
svg {
|
||||
margin-top: 2px;
|
||||
}
|
||||
> * {
|
||||
stroke: ${({ theme }) => theme.text1};
|
||||
}
|
||||
`
|
||||
|
||||
export default function Header() {
|
||||
const { account } = useActiveWeb3React()
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
|
||||
const userEthBalance = useETHBalances(account ? [account] : [])?.[account ?? '']
|
||||
// const [isDark] = useDarkModeManager()
|
||||
const [darkMode, toggleDarkMode] = useDarkModeManager()
|
||||
const [darkMode] = useDarkModeManager()
|
||||
|
||||
const toggleClaimModal = useToggleSelfClaimModal()
|
||||
|
||||
@@ -302,19 +255,18 @@ export default function Header() {
|
||||
|
||||
const scrollY = useScrollPosition()
|
||||
|
||||
const { infoLink } = CHAIN_INFO[chainId ? chainId : SupportedChainId.MAINNET]
|
||||
return (
|
||||
<HeaderFrame showBackground={scrollY > 45}>
|
||||
<ClaimModal />
|
||||
<Modal isOpen={showUniBalanceModal} onDismiss={() => setShowUniBalanceModal(false)}>
|
||||
<UniBalanceContent setShowUniBalanceModal={setShowUniBalanceModal} />
|
||||
</Modal>
|
||||
<HeaderRow>
|
||||
<Title href=".">
|
||||
<UniIcon>
|
||||
<img width={'24px'} src={darkMode ? LogoDark : Logo} alt="logo" />
|
||||
</UniIcon>
|
||||
</Title>
|
||||
</HeaderRow>
|
||||
<Title href=".">
|
||||
<UniIcon>
|
||||
<img width={'24px'} src={darkMode ? LogoDark : Logo} alt="logo" />
|
||||
</UniIcon>
|
||||
</Title>
|
||||
<HeaderLinks>
|
||||
<StyledNavLink id={`swap-nav-link`} to={'/swap'}>
|
||||
<Trans>Swap</Trans>
|
||||
@@ -332,19 +284,20 @@ export default function Header() {
|
||||
>
|
||||
<Trans>Pool</Trans>
|
||||
</StyledNavLink>
|
||||
<StyledNavLink id={`stake-nav-link`} to={'/vote'}>
|
||||
<Trans>Vote</Trans>
|
||||
</StyledNavLink>
|
||||
<StyledExternalLink id={`stake-nav-link`} href={'https://info.uniswap.org'}>
|
||||
{chainId && chainId === SupportedChainId.MAINNET && (
|
||||
<StyledNavLink id={`stake-nav-link`} to={'/vote'}>
|
||||
<Trans>Vote</Trans>
|
||||
</StyledNavLink>
|
||||
)}
|
||||
<StyledExternalLink id={`stake-nav-link`} href={infoLink}>
|
||||
<Trans>Charts</Trans>
|
||||
<sup>↗</sup>
|
||||
</StyledExternalLink>
|
||||
</HeaderLinks>
|
||||
|
||||
<HeaderControls>
|
||||
<NetworkCard />
|
||||
<HeaderElement>
|
||||
<HideSmall>
|
||||
<NetworkCard />
|
||||
</HideSmall>
|
||||
{availableClaim && !showClaimPopup && (
|
||||
<UNIWrapper onClick={toggleClaimModal}>
|
||||
<UNIAmount active={!!account && !availableClaim} style={{ pointerEvents: 'auto' }}>
|
||||
@@ -364,18 +317,13 @@ export default function Header() {
|
||||
<AccountElement active={!!account} style={{ pointerEvents: 'auto' }}>
|
||||
{account && userEthBalance ? (
|
||||
<BalanceText style={{ flexShrink: 0 }} pl="0.75rem" pr="0.5rem" fontWeight={500}>
|
||||
<Trans>{userEthBalance?.toSignificant(4)} ETH</Trans>
|
||||
<Trans>{userEthBalance?.toSignificant(3)} ETH</Trans>
|
||||
</BalanceText>
|
||||
) : null}
|
||||
<Web3Status />
|
||||
</AccountElement>
|
||||
</HeaderElement>
|
||||
<HeaderElementWrap>
|
||||
<StyledMenuButton onClick={() => toggleDarkMode()}>
|
||||
{darkMode ? <Moon size={20} /> : <Sun size={20} />}
|
||||
</StyledMenuButton>
|
||||
<Menu />
|
||||
</HeaderElementWrap>
|
||||
</HeaderElement>
|
||||
</HeaderControls>
|
||||
</HeaderFrame>
|
||||
)
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { useState, useCallback, useEffect, ReactNode } from 'react'
|
||||
import { LightCard } from 'components/Card'
|
||||
import { RowBetween } from 'components/Row'
|
||||
import { OutlineCard } from 'components/Card'
|
||||
import { Input as NumericalInput } from '../NumericalInput'
|
||||
import styled, { keyframes } from 'styled-components'
|
||||
import styled, { keyframes } from 'styled-components/macro'
|
||||
import { TYPE } from 'theme'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { ButtonPrimary } from 'components/Button'
|
||||
import { ButtonGray } from 'components/Button'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { formattedFeeAmount } from 'utils'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Plus, Minus } from 'react-feather'
|
||||
|
||||
const pulse = (color: string) => keyframes`
|
||||
0% {
|
||||
@@ -24,25 +23,37 @@ const pulse = (color: string) => keyframes`
|
||||
}
|
||||
`
|
||||
|
||||
const SmallButton = styled(ButtonPrimary)`
|
||||
/* background-color: ${({ theme }) => theme.bg2}; */
|
||||
border-radius: 8px;
|
||||
padding: 4px 6px;
|
||||
width: 48%;
|
||||
const InputRow = styled.div`
|
||||
display: grid;
|
||||
|
||||
grid-template-columns: 30px 1fr 30px;
|
||||
`
|
||||
|
||||
const FocusedOutlineCard = styled(LightCard)<{ active?: boolean; pulsing?: boolean }>`
|
||||
const SmallButton = styled(ButtonGray)`
|
||||
border-radius: 8px;
|
||||
padding: 4px;
|
||||
`
|
||||
|
||||
const FocusedOutlineCard = styled(OutlineCard)<{ active?: boolean; pulsing?: boolean }>`
|
||||
border-color: ${({ active, theme }) => active && theme.blue1};
|
||||
padding: 12px;
|
||||
animation: ${({ pulsing, theme }) => pulsing && pulse(theme.blue1)} 0.8s linear;
|
||||
`
|
||||
|
||||
const StyledInput = styled(NumericalInput)<{ usePercent?: boolean }>`
|
||||
/* background-color: ${({ theme }) => theme.bg0}; */
|
||||
background-color: transparent;
|
||||
text-align: center;
|
||||
margin-right: 12px;
|
||||
width: 100%;
|
||||
font-weight: 500;
|
||||
padding: 0 10px;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
font-size: 16px;
|
||||
`};
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToExtraSmall`
|
||||
font-size: 12px;
|
||||
`};
|
||||
`
|
||||
|
||||
const InputTitle = styled(TYPE.small)`
|
||||
@@ -51,11 +62,17 @@ const InputTitle = styled(TYPE.small)`
|
||||
font-weight: 500;
|
||||
`
|
||||
|
||||
const ButtonLabel = styled(TYPE.white)<{ disabled: boolean }>`
|
||||
color: ${({ theme, disabled }) => (disabled ? theme.text2 : theme.text1)} !important;
|
||||
`
|
||||
|
||||
interface StepCounterProps {
|
||||
value: string
|
||||
onUserInput: (value: string) => void
|
||||
decrement: () => string
|
||||
increment: () => string
|
||||
decrementDisabled?: boolean
|
||||
incrementDisabled?: boolean
|
||||
feeAmount?: FeeAmount
|
||||
label?: string
|
||||
width?: string
|
||||
@@ -69,7 +86,8 @@ const StepCounter = ({
|
||||
value,
|
||||
decrement,
|
||||
increment,
|
||||
feeAmount,
|
||||
decrementDisabled = false,
|
||||
incrementDisabled = false,
|
||||
width,
|
||||
locked,
|
||||
onUserInput,
|
||||
@@ -87,9 +105,6 @@ const StepCounter = ({
|
||||
// animation if parent value updates local value
|
||||
const [pulsing, setPulsing] = useState<boolean>(false)
|
||||
|
||||
// format fee amount
|
||||
const feeAmountFormatted = feeAmount ? formattedFeeAmount(feeAmount * 2) : ''
|
||||
|
||||
const handleOnFocus = () => {
|
||||
setUseLocalValue(true)
|
||||
setActive(true)
|
||||
@@ -126,39 +141,45 @@ const StepCounter = ({
|
||||
|
||||
return (
|
||||
<FocusedOutlineCard pulsing={pulsing} active={active} onFocus={handleOnFocus} onBlur={handleOnBlur} width={width}>
|
||||
<AutoColumn gap="6px" style={{ marginBottom: '12px' }}>
|
||||
<AutoColumn gap="6px">
|
||||
<InputTitle fontSize={12} textAlign="center">
|
||||
{title}
|
||||
</InputTitle>
|
||||
<StyledInput
|
||||
className="rate-input-0"
|
||||
value={localValue}
|
||||
fontSize="20px"
|
||||
disabled={locked}
|
||||
onUserInput={(val) => {
|
||||
setLocalValue(val)
|
||||
}}
|
||||
/>
|
||||
|
||||
<InputRow>
|
||||
{!locked && (
|
||||
<SmallButton onClick={handleDecrement} disabled={decrementDisabled}>
|
||||
<ButtonLabel disabled={decrementDisabled} fontSize="12px">
|
||||
<Minus size={18} />
|
||||
</ButtonLabel>
|
||||
</SmallButton>
|
||||
)}
|
||||
|
||||
<StyledInput
|
||||
className="rate-input-0"
|
||||
value={localValue}
|
||||
fontSize="20px"
|
||||
disabled={locked}
|
||||
onUserInput={(val) => {
|
||||
setLocalValue(val)
|
||||
}}
|
||||
/>
|
||||
|
||||
{!locked && (
|
||||
<SmallButton onClick={handleIncrement} disabled={incrementDisabled}>
|
||||
<ButtonLabel disabled={incrementDisabled} fontSize="12px">
|
||||
<Plus size={18} />
|
||||
</ButtonLabel>
|
||||
</SmallButton>
|
||||
)}
|
||||
</InputRow>
|
||||
|
||||
<InputTitle fontSize={12} textAlign="center">
|
||||
<Trans>
|
||||
{tokenB} per {tokenA}
|
||||
</Trans>
|
||||
</InputTitle>
|
||||
</AutoColumn>
|
||||
{!locked ? (
|
||||
<RowBetween>
|
||||
<SmallButton onClick={handleDecrement}>
|
||||
<TYPE.white fontSize="12px">
|
||||
<Trans>-{feeAmountFormatted}%</Trans>
|
||||
</TYPE.white>
|
||||
</SmallButton>
|
||||
<SmallButton onClick={handleIncrement}>
|
||||
<TYPE.white fontSize="12px">
|
||||
<Trans>+{feeAmountFormatted}%</Trans>
|
||||
</TYPE.white>
|
||||
</SmallButton>
|
||||
</RowBetween>
|
||||
) : null}
|
||||
</FocusedOutlineCard>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
export const dummyData = [
|
||||
{ time: '2018-10-19', value: 35.98 },
|
||||
{ time: '2018-10-22', value: 35.75 },
|
||||
{ time: '2018-10-23', value: 35.65 },
|
||||
{ time: '2018-10-24', value: 34.12 },
|
||||
{ time: '2018-10-25', value: 35.84 },
|
||||
{ time: '2018-10-26', value: 35.24 },
|
||||
{ time: '2018-10-29', value: 35.99 },
|
||||
{ time: '2018-10-30', value: 37.71 },
|
||||
{ time: '2018-10-31', value: 38.14 },
|
||||
{ time: '2018-11-01', value: 37.95 },
|
||||
{ time: '2018-11-02', value: 37.66 },
|
||||
{ time: '2018-11-05', value: 38.02 },
|
||||
{ time: '2018-11-06', value: 37.73 },
|
||||
{ time: '2018-11-07', value: 38.3 },
|
||||
{ time: '2018-11-08', value: 38.3 },
|
||||
{ time: '2018-11-09', value: 38.34 },
|
||||
{ time: '2018-11-12', value: 38.0 },
|
||||
{ time: '2018-11-13', value: 37.72 },
|
||||
{ time: '2018-11-14', value: 38.29 },
|
||||
{ time: '2018-11-15', value: 38.49 },
|
||||
{ time: '2018-11-16', value: 38.59 },
|
||||
{ time: '2018-11-19', value: 38.18 },
|
||||
{ time: '2018-11-20', value: 36.76 },
|
||||
{ time: '2018-11-21', value: 37.51 },
|
||||
{ time: '2018-11-23', value: 37.39 },
|
||||
{ time: '2018-11-26', value: 37.77 },
|
||||
{ time: '2018-11-27', value: 38.36 },
|
||||
{ time: '2018-11-28', value: 39.06 },
|
||||
{ time: '2018-11-29', value: 39.42 },
|
||||
{ time: '2018-11-30', value: 39.01 },
|
||||
{ time: '2018-12-03', value: 39.15 },
|
||||
{ time: '2018-12-04', value: 37.69 },
|
||||
{ time: '2018-12-06', value: 37.88 },
|
||||
{ time: '2018-12-07', value: 37.41 },
|
||||
{ time: '2018-12-10', value: 37.35 },
|
||||
{ time: '2018-12-11', value: 36.84 },
|
||||
{ time: '2018-12-12', value: 36.98 },
|
||||
{ time: '2018-12-13', value: 36.76 },
|
||||
{ time: '2018-12-14', value: 36.34 },
|
||||
{ time: '2018-12-17', value: 36.21 },
|
||||
{ time: '2018-12-18', value: 35.65 },
|
||||
{ time: '2018-12-19', value: 35.19 },
|
||||
{ time: '2018-12-20', value: 34.62 },
|
||||
{ time: '2018-12-21', value: 33.75 },
|
||||
{ time: '2018-12-24', value: 33.07 },
|
||||
{ time: '2018-12-26', value: 34.14 },
|
||||
{ time: '2018-12-27', value: 34.47 },
|
||||
{ time: '2018-12-28', value: 34.35 },
|
||||
{ time: '2018-12-31', value: 34.05 },
|
||||
{ time: '2019-01-02', value: 34.37 },
|
||||
{ time: '2019-01-03', value: 34.64 },
|
||||
{ time: '2019-01-04', value: 35.81 },
|
||||
{ time: '2019-01-07', value: 35.43 },
|
||||
{ time: '2019-01-08', value: 35.72 },
|
||||
{ time: '2019-01-09', value: 36.06 },
|
||||
{ time: '2019-01-10', value: 35.82 },
|
||||
{ time: '2019-01-11', value: 35.63 },
|
||||
{ time: '2019-01-14', value: 35.77 },
|
||||
{ time: '2019-01-15', value: 35.83 },
|
||||
{ time: '2019-01-16', value: 35.9 },
|
||||
{ time: '2019-01-17', value: 35.91 },
|
||||
{ time: '2019-01-18', value: 36.21 },
|
||||
{ time: '2019-01-22', value: 34.97 },
|
||||
{ time: '2019-01-23', value: 36.89 },
|
||||
{ time: '2019-01-24', value: 36.24 },
|
||||
{ time: '2019-01-25', value: 35.78 },
|
||||
{ time: '2019-01-28', value: 35.37 },
|
||||
{ time: '2019-01-29', value: 36.08 },
|
||||
{ time: '2019-01-30', value: 35.43 },
|
||||
{ time: '2019-01-31', value: 36.57 },
|
||||
{ time: '2019-02-01', value: 36.79 },
|
||||
{ time: '2019-02-04', value: 36.77 },
|
||||
{ time: '2019-02-05', value: 37.15 },
|
||||
{ time: '2019-02-06', value: 37.17 },
|
||||
{ time: '2019-02-07', value: 37.68 },
|
||||
{ time: '2019-02-08', value: 37.6 },
|
||||
{ time: '2019-02-11', value: 37.0 },
|
||||
{ time: '2019-02-12', value: 37.24 },
|
||||
{ time: '2019-02-13', value: 37.03 },
|
||||
{ time: '2019-02-14', value: 37.26 },
|
||||
{ time: '2019-02-15', value: 37.77 },
|
||||
{ time: '2019-02-19', value: 37.55 },
|
||||
{ time: '2019-02-20', value: 37.79 },
|
||||
{ time: '2019-02-21', value: 38.47 },
|
||||
{ time: '2019-02-22', value: 38.61 },
|
||||
{ time: '2019-02-25', value: 38.57 },
|
||||
{ time: '2019-02-26', value: 38.8 },
|
||||
{ time: '2019-02-27', value: 38.53 },
|
||||
{ time: '2019-02-28', value: 38.67 },
|
||||
{ time: '2019-03-01', value: 39.1 },
|
||||
{ time: '2019-03-04', value: 38.73 },
|
||||
{ time: '2019-03-05', value: 38.72 },
|
||||
{ time: '2019-03-06', value: 38.61 },
|
||||
{ time: '2019-03-07', value: 38.38 },
|
||||
{ time: '2019-03-08', value: 38.19 },
|
||||
{ time: '2019-03-11', value: 39.17 },
|
||||
{ time: '2019-03-12', value: 39.49 },
|
||||
{ time: '2019-03-13', value: 39.56 },
|
||||
{ time: '2019-03-14', value: 39.87 },
|
||||
{ time: '2019-03-15', value: 40.47 },
|
||||
{ time: '2019-03-18', value: 39.92 },
|
||||
{ time: '2019-03-19', value: 39.78 },
|
||||
{ time: '2019-03-20', value: 39.47 },
|
||||
{ time: '2019-03-21', value: 40.05 },
|
||||
{ time: '2019-03-22', value: 39.46 },
|
||||
{ time: '2019-03-25', value: 39.18 },
|
||||
{ time: '2019-03-26', value: 39.63 },
|
||||
{ time: '2019-03-27', value: 40.21 },
|
||||
{ time: '2019-03-28', value: 40.42 },
|
||||
{ time: '2019-03-29', value: 39.98 },
|
||||
{ time: '2019-04-01', value: 40.31 },
|
||||
{ time: '2019-04-02', value: 40.02 },
|
||||
{ time: '2019-04-03', value: 40.27 },
|
||||
{ time: '2019-04-04', value: 40.41 },
|
||||
{ time: '2019-04-05', value: 40.42 },
|
||||
{ time: '2019-04-08', value: 40.71 },
|
||||
{ time: '2019-04-09', value: 41.04 },
|
||||
{ time: '2019-04-10', value: 41.08 },
|
||||
{ time: '2019-04-11', value: 41.04 },
|
||||
{ time: '2019-04-12', value: 41.3 },
|
||||
{ time: '2019-04-15', value: 41.78 },
|
||||
{ time: '2019-04-16', value: 41.97 },
|
||||
{ time: '2019-04-17', value: 42.57 },
|
||||
{ time: '2019-04-18', value: 42.43 },
|
||||
{ time: '2019-04-22', value: 42.0 },
|
||||
{ time: '2019-04-23', value: 41.99 },
|
||||
{ time: '2019-04-24', value: 41.85 },
|
||||
{ time: '2019-04-25', value: 42.93 },
|
||||
{ time: '2019-04-26', value: 43.08 },
|
||||
{ time: '2019-04-29', value: 43.45 },
|
||||
{ time: '2019-04-30', value: 43.53 },
|
||||
{ time: '2019-05-01', value: 43.42 },
|
||||
{ time: '2019-05-02', value: 42.65 },
|
||||
{ time: '2019-05-03', value: 43.29 },
|
||||
{ time: '2019-05-06', value: 43.3 },
|
||||
{ time: '2019-05-07', value: 42.76 },
|
||||
{ time: '2019-05-08', value: 42.55 },
|
||||
{ time: '2019-05-09', value: 42.92 },
|
||||
{ time: '2019-05-10', value: 43.15 },
|
||||
{ time: '2019-05-13', value: 42.28 },
|
||||
{ time: '2019-05-14', value: 42.91 },
|
||||
{ time: '2019-05-15', value: 42.49 },
|
||||
{ time: '2019-05-16', value: 43.19 },
|
||||
{ time: '2019-05-17', value: 43.54 },
|
||||
{ time: '2019-05-20', value: 42.78 },
|
||||
{ time: '2019-05-21', value: 43.29 },
|
||||
{ time: '2019-05-22', value: 43.3 },
|
||||
{ time: '2019-05-23', value: 42.73 },
|
||||
{ time: '2019-05-24', value: 42.67 },
|
||||
{ time: '2019-05-28', value: 42.75 },
|
||||
]
|
||||
@@ -10,7 +10,7 @@ const Wrapper = styled(Card)`
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
background-color: ${({ theme }) => theme.bg0}
|
||||
background-color: ${({ theme }) => theme.bg0};
|
||||
flex-direction: column;
|
||||
> * {
|
||||
font-size: 1rem;
|
||||
@@ -19,7 +19,7 @@ const Wrapper = styled(Card)`
|
||||
|
||||
const DEFAULT_HEIGHT = 300
|
||||
|
||||
export type LineChartProps = {
|
||||
type LineChartProps = {
|
||||
data: any[]
|
||||
color?: string | undefined
|
||||
height?: number | undefined
|
||||
|
||||
44
src/components/LiquidityChartRangeInput/Area.tsx
Normal file
44
src/components/LiquidityChartRangeInput/Area.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import { area, curveStepAfter, ScaleLinear } from 'd3'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ChartEntry } from './types'
|
||||
import inRange from 'lodash/inRange'
|
||||
|
||||
const Path = styled.path<{ fill: string | undefined }>`
|
||||
opacity: 0.5;
|
||||
stroke: ${({ fill, theme }) => fill ?? theme.blue2};
|
||||
fill: ${({ fill, theme }) => fill ?? theme.blue2};
|
||||
`
|
||||
|
||||
export const Area = ({
|
||||
series,
|
||||
xScale,
|
||||
yScale,
|
||||
xValue,
|
||||
yValue,
|
||||
fill,
|
||||
}: {
|
||||
series: ChartEntry[]
|
||||
xScale: ScaleLinear<number, number>
|
||||
yScale: ScaleLinear<number, number>
|
||||
xValue: (d: ChartEntry) => number
|
||||
yValue: (d: ChartEntry) => number
|
||||
fill?: string | undefined
|
||||
}) =>
|
||||
useMemo(
|
||||
() => (
|
||||
<Path
|
||||
fill={fill}
|
||||
d={
|
||||
area()
|
||||
.curve(curveStepAfter)
|
||||
.x((d: unknown) => xScale(xValue(d as ChartEntry)))
|
||||
.y1((d: unknown) => yScale(yValue(d as ChartEntry)))
|
||||
.y0(yScale(0))(
|
||||
series.filter((d) => inRange(xScale(xValue(d)), 0, innerWidth)) as Iterable<[number, number]>
|
||||
) ?? undefined
|
||||
}
|
||||
/>
|
||||
),
|
||||
[fill, series, xScale, xValue, yScale, yValue]
|
||||
)
|
||||
43
src/components/LiquidityChartRangeInput/AxisBottom.tsx
Normal file
43
src/components/LiquidityChartRangeInput/AxisBottom.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import { Axis as d3Axis, axisBottom, NumberValue, ScaleLinear, select } from 'd3'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const StyledGroup = styled.g`
|
||||
line {
|
||||
display: none;
|
||||
}
|
||||
|
||||
text {
|
||||
color: ${({ theme }) => theme.text2};
|
||||
transform: translateY(5px);
|
||||
}
|
||||
`
|
||||
|
||||
const Axis = ({ axisGenerator }: { axisGenerator: d3Axis<NumberValue> }) => {
|
||||
const axisRef = (axis: SVGGElement) => {
|
||||
axis &&
|
||||
select(axis)
|
||||
.call(axisGenerator)
|
||||
.call((g) => g.select('.domain').remove())
|
||||
}
|
||||
|
||||
return <g ref={axisRef} />
|
||||
}
|
||||
|
||||
export const AxisBottom = ({
|
||||
xScale,
|
||||
innerHeight,
|
||||
offset = 0,
|
||||
}: {
|
||||
xScale: ScaleLinear<number, number>
|
||||
innerHeight: number
|
||||
offset?: number
|
||||
}) =>
|
||||
useMemo(
|
||||
() => (
|
||||
<StyledGroup transform={`translate(0, ${innerHeight + offset})`}>
|
||||
<Axis axisGenerator={axisBottom(xScale).ticks(6)} />
|
||||
</StyledGroup>
|
||||
),
|
||||
[innerHeight, offset, xScale]
|
||||
)
|
||||
240
src/components/LiquidityChartRangeInput/Brush.tsx
Normal file
240
src/components/LiquidityChartRangeInput/Brush.tsx
Normal file
@@ -0,0 +1,240 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { BrushBehavior, brushX, D3BrushEvent, ScaleLinear, select } from 'd3'
|
||||
import styled from 'styled-components/macro'
|
||||
import { brushHandleAccentPath, brushHandlePath } from 'components/LiquidityChartRangeInput/svg'
|
||||
import usePrevious from 'hooks/usePrevious'
|
||||
|
||||
const Handle = styled.path<{ color: string }>`
|
||||
cursor: ew-resize;
|
||||
pointer-events: none;
|
||||
|
||||
stroke-width: 4;
|
||||
stroke: ${({ color }) => color};
|
||||
fill: ${({ color }) => color};
|
||||
`
|
||||
|
||||
const HandleAccent = styled.path`
|
||||
cursor: ew-resize;
|
||||
pointer-events: none;
|
||||
|
||||
stroke-width: 1.5;
|
||||
stroke: ${({ theme }) => theme.white};
|
||||
opacity: 0.6;
|
||||
`
|
||||
|
||||
const LabelGroup = styled.g<{ visible: boolean }>`
|
||||
opacity: ${({ visible }) => (visible ? '1' : '0')};
|
||||
transition: opacity 300ms;
|
||||
`
|
||||
|
||||
const TooltipBackground = styled.rect`
|
||||
fill: ${({ theme }) => theme.bg2};
|
||||
`
|
||||
|
||||
const Tooltip = styled.text`
|
||||
text-anchor: middle;
|
||||
font-size: 13px;
|
||||
fill: ${({ theme }) => theme.text1};
|
||||
`
|
||||
|
||||
// flips the handles draggers when close to the container edges
|
||||
const FLIP_HANDLE_THRESHOLD_PX = 20
|
||||
|
||||
const compare = (a1: [number, number], a2: [number, number]): boolean => a1[0] !== a2[0] || a1[1] !== a2[1]
|
||||
|
||||
export const Brush = ({
|
||||
id,
|
||||
xScale,
|
||||
interactive,
|
||||
brushLabelValue,
|
||||
brushExtent,
|
||||
setBrushExtent,
|
||||
innerWidth,
|
||||
innerHeight,
|
||||
westHandleColor,
|
||||
eastHandleColor,
|
||||
}: {
|
||||
id: string
|
||||
xScale: ScaleLinear<number, number>
|
||||
interactive: boolean
|
||||
brushLabelValue: (d: 'w' | 'e', x: number) => string
|
||||
brushExtent: [number, number]
|
||||
setBrushExtent: (extent: [number, number]) => void
|
||||
innerWidth: number
|
||||
innerHeight: number
|
||||
westHandleColor: string
|
||||
eastHandleColor: string
|
||||
}) => {
|
||||
const brushRef = useRef<SVGGElement | null>(null)
|
||||
const brushBehavior = useRef<BrushBehavior<SVGGElement> | null>(null)
|
||||
|
||||
// only used to drag the handles on brush for performance
|
||||
const [localBrushExtent, setLocalBrushExtent] = useState<[number, number] | null>(brushExtent)
|
||||
const [showLabels, setShowLabels] = useState(false)
|
||||
const [hovering, setHovering] = useState(false)
|
||||
|
||||
const previousBrushExtent = usePrevious(brushExtent)
|
||||
|
||||
const brushed = useCallback(
|
||||
({ type, selection }: D3BrushEvent<unknown>) => {
|
||||
if (!selection) {
|
||||
setLocalBrushExtent(null)
|
||||
return
|
||||
}
|
||||
|
||||
const scaled = (selection as [number, number]).map(xScale.invert) as [number, number]
|
||||
|
||||
// avoid infinite render loop by checking for change
|
||||
if (type === 'end' && compare(brushExtent, scaled)) {
|
||||
setBrushExtent(scaled)
|
||||
}
|
||||
|
||||
setLocalBrushExtent(scaled)
|
||||
},
|
||||
[xScale, brushExtent, setBrushExtent]
|
||||
)
|
||||
|
||||
// keep local and external brush extent in sync
|
||||
// i.e. snap to ticks on bruhs end
|
||||
useEffect(() => {
|
||||
setLocalBrushExtent(brushExtent)
|
||||
}, [brushExtent])
|
||||
|
||||
// initialize the brush
|
||||
useEffect(() => {
|
||||
if (!brushRef.current) return
|
||||
|
||||
brushBehavior.current = brushX<SVGGElement>()
|
||||
.extent([
|
||||
[Math.max(0, xScale(0)), 0],
|
||||
[innerWidth, innerHeight],
|
||||
])
|
||||
.handleSize(30)
|
||||
.filter(() => interactive)
|
||||
.on('brush end', brushed)
|
||||
|
||||
brushBehavior.current(select(brushRef.current))
|
||||
|
||||
if (previousBrushExtent && compare(brushExtent, previousBrushExtent)) {
|
||||
select(brushRef.current)
|
||||
.transition()
|
||||
.call(brushBehavior.current.move as any, brushExtent.map(xScale))
|
||||
}
|
||||
|
||||
// brush linear gradient
|
||||
select(brushRef.current)
|
||||
.selectAll('.selection')
|
||||
.attr('stroke', 'none')
|
||||
.attr('fill-opacity', '0.1')
|
||||
.attr('fill', `url(#${id}-gradient-selection)`)
|
||||
}, [brushExtent, brushed, id, innerHeight, innerWidth, interactive, previousBrushExtent, xScale])
|
||||
|
||||
// respond to xScale changes only
|
||||
useEffect(() => {
|
||||
if (!brushRef.current || !brushBehavior.current) return
|
||||
|
||||
brushBehavior.current.move(select(brushRef.current) as any, brushExtent.map(xScale) as any)
|
||||
}, [brushExtent, xScale])
|
||||
|
||||
useEffect(() => {
|
||||
setShowLabels(true)
|
||||
const timeout = setTimeout(() => setShowLabels(false), 1500)
|
||||
return () => clearTimeout(timeout)
|
||||
}, [localBrushExtent])
|
||||
|
||||
const flipWestHandle = localBrushExtent && xScale(localBrushExtent[0]) > FLIP_HANDLE_THRESHOLD_PX
|
||||
const flipEastHandle = localBrushExtent && xScale(localBrushExtent[1]) > innerWidth - FLIP_HANDLE_THRESHOLD_PX
|
||||
|
||||
return useMemo(
|
||||
() => (
|
||||
<>
|
||||
<defs>
|
||||
<linearGradient id={`${id}-gradient-selection`} x1="0%" y1="100%" x2="100%" y2="100%">
|
||||
<stop stopColor={westHandleColor} />
|
||||
<stop stopColor={eastHandleColor} offset="1" />
|
||||
</linearGradient>
|
||||
|
||||
{/* clips at exactly the svg area */}
|
||||
<clipPath id={`${id}-brush-clip`}>
|
||||
<rect x="0" y="0" width={innerWidth} height="100%" />
|
||||
</clipPath>
|
||||
|
||||
<clipPath id={`${id}-handles-clip`}>
|
||||
<rect x="0" y="0" width="100%" height="100%" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
|
||||
{/* will host the d3 brush */}
|
||||
<g
|
||||
ref={brushRef}
|
||||
clipPath={`url(#${id}-brush-clip)`}
|
||||
onMouseEnter={() => setHovering(true)}
|
||||
onMouseLeave={() => setHovering(false)}
|
||||
/>
|
||||
|
||||
{/* custom brush handles */}
|
||||
{localBrushExtent && (
|
||||
<>
|
||||
{/* west handle */}
|
||||
<g
|
||||
transform={`translate(${Math.max(0, xScale(localBrushExtent[0]))}, 0), scale(${
|
||||
flipWestHandle ? '-1' : '1'
|
||||
}, 1)`}
|
||||
>
|
||||
<g clipPath={`url(#${id}-handles-clip)`}>
|
||||
<Handle color={westHandleColor} d={brushHandlePath(innerHeight)} />
|
||||
<HandleAccent d={brushHandleAccentPath()} />
|
||||
</g>
|
||||
|
||||
<LabelGroup
|
||||
transform={`translate(50,0), scale(${flipWestHandle ? '1' : '-1'}, 1)`}
|
||||
visible={showLabels || hovering}
|
||||
>
|
||||
<TooltipBackground y="0" x="-30" height="30" width="60" rx="8" />
|
||||
<Tooltip transform={`scale(-1, 1)`} y="15" dominantBaseline="middle">
|
||||
{brushLabelValue('w', localBrushExtent[0])}
|
||||
</Tooltip>
|
||||
</LabelGroup>
|
||||
</g>
|
||||
|
||||
{/* east handle */}
|
||||
<g
|
||||
transform={`translate(${Math.min(xScale(localBrushExtent[1]), innerWidth)}, 0), scale(${
|
||||
flipEastHandle ? '-1' : '1'
|
||||
}, 1)`}
|
||||
>
|
||||
<g clipPath={`url(#${id}-handles-clip)`}>
|
||||
<Handle color={eastHandleColor} d={brushHandlePath(innerHeight)} />
|
||||
<HandleAccent d={brushHandleAccentPath()} />
|
||||
</g>
|
||||
|
||||
<LabelGroup
|
||||
transform={`translate(50,0), scale(${flipEastHandle ? '-1' : '1'}, 1)`}
|
||||
visible={showLabels || hovering}
|
||||
>
|
||||
<TooltipBackground y="0" x="-30" height="30" width="60" rx="8" />
|
||||
<Tooltip y="15" dominantBaseline="middle">
|
||||
{brushLabelValue('e', localBrushExtent[1])}
|
||||
</Tooltip>
|
||||
</LabelGroup>
|
||||
</g>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
[
|
||||
brushLabelValue,
|
||||
eastHandleColor,
|
||||
flipEastHandle,
|
||||
flipWestHandle,
|
||||
hovering,
|
||||
id,
|
||||
innerHeight,
|
||||
innerWidth,
|
||||
localBrushExtent,
|
||||
showLabels,
|
||||
westHandleColor,
|
||||
xScale,
|
||||
]
|
||||
)
|
||||
}
|
||||
136
src/components/LiquidityChartRangeInput/Chart.tsx
Normal file
136
src/components/LiquidityChartRangeInput/Chart.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import { max, scaleLinear, ZoomTransform } from 'd3'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Area } from './Area'
|
||||
import { AxisBottom } from './AxisBottom'
|
||||
import { Brush } from './Brush'
|
||||
import { Line } from './Line'
|
||||
import { ChartEntry, LiquidityChartRangeInputProps } from './types'
|
||||
import Zoom from './Zoom'
|
||||
|
||||
export const xAccessor = (d: ChartEntry) => d.price0
|
||||
export const yAccessor = (d: ChartEntry) => d.activeLiquidity
|
||||
|
||||
export function Chart({
|
||||
id = 'liquidityChartRangeInput',
|
||||
data: { series, current },
|
||||
styles,
|
||||
dimensions: { width, height },
|
||||
margins,
|
||||
interactive = true,
|
||||
brushDomain,
|
||||
brushLabels,
|
||||
onBrushDomainChange,
|
||||
zoomLevels,
|
||||
}: LiquidityChartRangeInputProps) {
|
||||
const svgRef = useRef<SVGSVGElement | null>(null)
|
||||
|
||||
const [zoom, setZoom] = useState<ZoomTransform | null>(null)
|
||||
|
||||
const [innerHeight, innerWidth] = useMemo(
|
||||
() => [height - margins.top - margins.bottom, width - margins.left - margins.right],
|
||||
[width, height, margins]
|
||||
)
|
||||
|
||||
const { xScale, yScale } = useMemo(() => {
|
||||
const scales = {
|
||||
xScale: scaleLinear()
|
||||
.domain([current * zoomLevels.initialMin, current * zoomLevels.initialMax] as number[])
|
||||
.range([0, innerWidth]),
|
||||
yScale: scaleLinear()
|
||||
.domain([0, max(series, yAccessor)] as number[])
|
||||
.range([innerHeight, 0]),
|
||||
}
|
||||
|
||||
if (zoom) {
|
||||
const newXscale = zoom.rescaleX(scales.xScale)
|
||||
scales.xScale.domain(newXscale.domain())
|
||||
}
|
||||
|
||||
return scales
|
||||
}, [current, zoomLevels.initialMin, zoomLevels.initialMax, innerWidth, series, innerHeight, zoom])
|
||||
|
||||
useEffect(() => {
|
||||
// reset zoom as necessary
|
||||
setZoom(null)
|
||||
}, [zoomLevels])
|
||||
|
||||
useEffect(() => {
|
||||
if (!brushDomain) {
|
||||
onBrushDomainChange(xScale.domain() as [number, number])
|
||||
}
|
||||
}, [brushDomain, onBrushDomainChange, xScale])
|
||||
|
||||
// ensures the brush remains in view and adapts to zooms
|
||||
xScale.clamp(true)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Zoom
|
||||
svg={svgRef.current}
|
||||
xScale={xScale}
|
||||
setZoom={setZoom}
|
||||
innerWidth={innerWidth}
|
||||
innerHeight={innerHeight}
|
||||
showClear={Boolean(zoom && zoom.k !== 1)}
|
||||
zoomLevels={zoomLevels}
|
||||
/>
|
||||
<svg ref={svgRef} width="100%" height="100%" viewBox={`0 0 ${width} ${height}`} style={{ overflow: 'visible' }}>
|
||||
<defs>
|
||||
<clipPath id={`${id}-chart-clip`}>
|
||||
<rect x="0" y="0" width={innerWidth} height={height} />
|
||||
</clipPath>
|
||||
|
||||
{brushDomain && (
|
||||
// mask to highlight selected area
|
||||
<mask id={`${id}-chart-area-mask`}>
|
||||
<rect
|
||||
fill="white"
|
||||
x={xScale(brushDomain[0])}
|
||||
y="0"
|
||||
width={xScale(brushDomain[1]) - xScale(brushDomain[0])}
|
||||
height={innerHeight}
|
||||
/>
|
||||
</mask>
|
||||
)}
|
||||
</defs>
|
||||
|
||||
<g transform={`translate(${margins.left},${margins.top})`}>
|
||||
<g clipPath={`url(#${id}-chart-clip)`}>
|
||||
<Area series={series} xScale={xScale} yScale={yScale} xValue={xAccessor} yValue={yAccessor} />
|
||||
|
||||
{brushDomain && (
|
||||
// duplicate area chart with mask for selected area
|
||||
<g mask={`url(#${id}-chart-area-mask)`}>
|
||||
<Area
|
||||
series={series}
|
||||
xScale={xScale}
|
||||
yScale={yScale}
|
||||
xValue={xAccessor}
|
||||
yValue={yAccessor}
|
||||
fill={styles.area.selection}
|
||||
/>
|
||||
</g>
|
||||
)}
|
||||
|
||||
<Line value={current} xScale={xScale} innerHeight={innerHeight} />
|
||||
|
||||
<AxisBottom xScale={xScale} innerHeight={innerHeight} />
|
||||
</g>
|
||||
|
||||
<Brush
|
||||
id={id}
|
||||
xScale={xScale}
|
||||
interactive={interactive}
|
||||
brushLabelValue={brushLabels}
|
||||
brushExtent={brushDomain ?? (xScale.domain() as [number, number])}
|
||||
innerWidth={innerWidth}
|
||||
innerHeight={innerHeight}
|
||||
setBrushExtent={onBrushDomainChange}
|
||||
westHandleColor={styles.brush.handle.west}
|
||||
eastHandleColor={styles.brush.handle.east}
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</>
|
||||
)
|
||||
}
|
||||
24
src/components/LiquidityChartRangeInput/Line.tsx
Normal file
24
src/components/LiquidityChartRangeInput/Line.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import { ScaleLinear } from 'd3'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const StyledLine = styled.line`
|
||||
opacity: 0.5;
|
||||
stroke-width: 2;
|
||||
stroke: ${({ theme }) => theme.text1};
|
||||
fill: none;
|
||||
`
|
||||
|
||||
export const Line = ({
|
||||
value,
|
||||
xScale,
|
||||
innerHeight,
|
||||
}: {
|
||||
value: number
|
||||
xScale: ScaleLinear<number, number>
|
||||
innerHeight: number
|
||||
}) =>
|
||||
useMemo(
|
||||
() => <StyledLine x1={xScale(value)} y1="0" x2={xScale(value)} y2={innerHeight} />,
|
||||
[value, xScale, innerHeight]
|
||||
)
|
||||
118
src/components/LiquidityChartRangeInput/Zoom.tsx
Normal file
118
src/components/LiquidityChartRangeInput/Zoom.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
import React, { useEffect, useMemo, useRef } from 'react'
|
||||
import { ButtonGray } from 'components/Button'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ScaleLinear, select, ZoomBehavior, zoom, ZoomTransform } from 'd3'
|
||||
import { RefreshCcw, ZoomIn, ZoomOut } from 'react-feather'
|
||||
import { ZoomLevels } from './types'
|
||||
|
||||
const Wrapper = styled.div<{ count: number }>`
|
||||
display: grid;
|
||||
grid-template-columns: repeat(${({ count }) => count.toString()}, 1fr);
|
||||
grid-gap: 6px;
|
||||
|
||||
position: absolute;
|
||||
top: -75px;
|
||||
right: 0;
|
||||
`
|
||||
|
||||
const Button = styled(ButtonGray)`
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
color: ${({ theme }) => theme.text1};
|
||||
}
|
||||
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 4px;
|
||||
`
|
||||
|
||||
export default function Zoom({
|
||||
svg,
|
||||
xScale,
|
||||
setZoom,
|
||||
innerWidth,
|
||||
innerHeight,
|
||||
showClear,
|
||||
zoomLevels,
|
||||
}: {
|
||||
svg: SVGSVGElement | null
|
||||
xScale: ScaleLinear<number, number>
|
||||
setZoom: (transform: ZoomTransform) => void
|
||||
innerWidth: number
|
||||
innerHeight: number
|
||||
showClear: boolean
|
||||
zoomLevels: ZoomLevels
|
||||
}) {
|
||||
const zoomBehavior = useRef<ZoomBehavior<Element, unknown>>()
|
||||
|
||||
const [zoomIn, zoomOut, reset, initial] = useMemo(
|
||||
() => [
|
||||
() =>
|
||||
svg &&
|
||||
zoomBehavior.current &&
|
||||
select(svg as Element)
|
||||
.transition()
|
||||
.call(zoomBehavior.current.scaleBy, 2),
|
||||
() =>
|
||||
svg &&
|
||||
zoomBehavior.current &&
|
||||
select(svg as Element)
|
||||
.transition()
|
||||
.call(zoomBehavior.current.scaleBy, 0.5),
|
||||
() =>
|
||||
svg &&
|
||||
zoomBehavior.current &&
|
||||
select(svg as Element)
|
||||
.transition()
|
||||
.call(zoomBehavior.current.scaleTo, 1),
|
||||
() =>
|
||||
svg &&
|
||||
zoomBehavior.current &&
|
||||
select(svg as Element)
|
||||
.transition()
|
||||
.call(zoomBehavior.current.scaleTo, 0.5),
|
||||
],
|
||||
[svg, zoomBehavior]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!svg) return
|
||||
|
||||
zoomBehavior.current = zoom()
|
||||
.scaleExtent([zoomLevels.min, zoomLevels.max])
|
||||
.translateExtent([
|
||||
[0, 0],
|
||||
[innerWidth, innerHeight],
|
||||
])
|
||||
.extent([
|
||||
[0, 0],
|
||||
[innerWidth, innerHeight],
|
||||
])
|
||||
.on('zoom', ({ transform }: { transform: ZoomTransform }) => setZoom(transform))
|
||||
|
||||
select(svg as Element)
|
||||
.call(zoomBehavior.current)
|
||||
.on('mousedown.zoom', null)
|
||||
}, [innerHeight, innerWidth, setZoom, svg, xScale, zoomBehavior, zoomLevels, zoomLevels.max, zoomLevels.min])
|
||||
|
||||
useEffect(() => {
|
||||
// reset zoom to initial on zoomLevel chang
|
||||
initial()
|
||||
}, [initial, zoomLevels])
|
||||
|
||||
return (
|
||||
<Wrapper count={showClear ? 3 : 2}>
|
||||
{showClear && (
|
||||
<Button onClick={reset} disabled={false}>
|
||||
<RefreshCcw size={16} />
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={zoomIn} disabled={false}>
|
||||
<ZoomIn size={16} />
|
||||
</Button>
|
||||
<Button onClick={zoomOut} disabled={false}>
|
||||
<ZoomOut size={16} />
|
||||
</Button>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
56
src/components/LiquidityChartRangeInput/hooks.ts
Normal file
56
src/components/LiquidityChartRangeInput/hooks.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { usePoolActiveLiquidity } from 'hooks/usePoolTickData'
|
||||
import { ChartEntry } from './types'
|
||||
import JSBI from 'jsbi'
|
||||
|
||||
export interface TickProcessed {
|
||||
liquidityActive: JSBI
|
||||
price0: string
|
||||
}
|
||||
|
||||
export function useDensityChartData({
|
||||
currencyA,
|
||||
currencyB,
|
||||
feeAmount,
|
||||
}: {
|
||||
currencyA: Currency | undefined
|
||||
currencyB: Currency | undefined
|
||||
feeAmount: FeeAmount | undefined
|
||||
}) {
|
||||
const { isLoading, isUninitialized, isError, error, data } = usePoolActiveLiquidity(currencyA, currencyB, feeAmount)
|
||||
|
||||
const formatData = useCallback(() => {
|
||||
if (!data?.length) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const newData: ChartEntry[] = []
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const t: TickProcessed = data[i]
|
||||
|
||||
const chartEntry = {
|
||||
activeLiquidity: parseFloat(t.liquidityActive.toString()),
|
||||
price0: parseFloat(t.price0),
|
||||
}
|
||||
|
||||
if (chartEntry.activeLiquidity > 0) {
|
||||
newData.push(chartEntry)
|
||||
}
|
||||
}
|
||||
|
||||
return newData
|
||||
}, [data])
|
||||
|
||||
return useMemo(() => {
|
||||
return {
|
||||
isLoading,
|
||||
isUninitialized,
|
||||
isError,
|
||||
error,
|
||||
formattedData: !isLoading && !isUninitialized ? formatData() : undefined,
|
||||
}
|
||||
}, [isLoading, isUninitialized, isError, error, formatData])
|
||||
}
|
||||
197
src/components/LiquidityChartRangeInput/index.tsx
Normal file
197
src/components/LiquidityChartRangeInput/index.tsx
Normal file
@@ -0,0 +1,197 @@
|
||||
import React, { ReactNode, useCallback, useMemo } from 'react'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, Price, Token } from '@uniswap/sdk-core'
|
||||
import { AutoColumn, ColumnCenter } from 'components/Column'
|
||||
import Loader from 'components/Loader'
|
||||
import { useColor } from 'hooks/useColor'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import { saturate } from 'polished'
|
||||
import { BarChart2, Inbox, CloudOff } from 'react-feather'
|
||||
import { batch } from 'react-redux'
|
||||
import styled from 'styled-components/macro'
|
||||
import { TYPE } from '../../theme'
|
||||
import { Chart } from './Chart'
|
||||
import { useDensityChartData } from './hooks'
|
||||
import { format } from 'd3'
|
||||
import { Bound } from 'state/mint/v3/actions'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import ReactGA from 'react-ga'
|
||||
import { ZoomLevels } from './types'
|
||||
|
||||
const ZOOM_LEVELS: Record<FeeAmount, ZoomLevels> = {
|
||||
[FeeAmount.LOW]: {
|
||||
initialMin: 0.999,
|
||||
initialMax: 1.001,
|
||||
min: 0.001,
|
||||
max: 1.5,
|
||||
},
|
||||
[FeeAmount.MEDIUM]: {
|
||||
initialMin: 0.5,
|
||||
initialMax: 2,
|
||||
min: 0.01,
|
||||
max: 20,
|
||||
},
|
||||
[FeeAmount.HIGH]: {
|
||||
initialMin: 0.5,
|
||||
initialMax: 2,
|
||||
min: 0.01,
|
||||
max: 20,
|
||||
},
|
||||
}
|
||||
|
||||
const ChartWrapper = styled.div`
|
||||
position: relative;
|
||||
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
`
|
||||
|
||||
function InfoBox({ message, icon }: { message?: ReactNode; icon: ReactNode }) {
|
||||
return (
|
||||
<ColumnCenter style={{ height: '100%', justifyContent: 'center' }}>
|
||||
{icon}
|
||||
{message && (
|
||||
<TYPE.mediumHeader padding={10} marginTop="20px" textAlign="center">
|
||||
{message}
|
||||
</TYPE.mediumHeader>
|
||||
)}
|
||||
</ColumnCenter>
|
||||
)
|
||||
}
|
||||
|
||||
export default function LiquidityChartRangeInput({
|
||||
currencyA,
|
||||
currencyB,
|
||||
feeAmount,
|
||||
ticksAtLimit,
|
||||
price,
|
||||
priceLower,
|
||||
priceUpper,
|
||||
onLeftRangeInput,
|
||||
onRightRangeInput,
|
||||
interactive,
|
||||
}: {
|
||||
currencyA: Currency | undefined
|
||||
currencyB: Currency | undefined
|
||||
feeAmount?: FeeAmount
|
||||
ticksAtLimit: { [bound in Bound]?: boolean | undefined }
|
||||
price: number | undefined
|
||||
priceLower?: Price<Token, Token>
|
||||
priceUpper?: Price<Token, Token>
|
||||
onLeftRangeInput: (typedValue: string) => void
|
||||
onRightRangeInput: (typedValue: string) => void
|
||||
interactive: boolean
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
|
||||
const tokenAColor = useColor(currencyA?.wrapped)
|
||||
const tokenBColor = useColor(currencyB?.wrapped)
|
||||
|
||||
const { isLoading, isUninitialized, isError, error, formattedData } = useDensityChartData({
|
||||
currencyA,
|
||||
currencyB,
|
||||
feeAmount,
|
||||
})
|
||||
|
||||
const onBrushDomainChangeEnded = useCallback(
|
||||
(domain) => {
|
||||
let leftRangeValue = Number(domain[0])
|
||||
const rightRangeValue = Number(domain[1])
|
||||
|
||||
if (leftRangeValue <= 0) {
|
||||
leftRangeValue = 1 / 10 ** 6
|
||||
}
|
||||
|
||||
batch(() => {
|
||||
// simulate user input for auto-formatting and other validations
|
||||
leftRangeValue > 0 && onLeftRangeInput(leftRangeValue.toFixed(6))
|
||||
rightRangeValue > 0 && onRightRangeInput(rightRangeValue.toFixed(6))
|
||||
})
|
||||
},
|
||||
[onLeftRangeInput, onRightRangeInput]
|
||||
)
|
||||
|
||||
interactive = interactive && Boolean(formattedData?.length)
|
||||
|
||||
const brushDomain: [number, number] | undefined = useMemo(() => {
|
||||
const isSorted = currencyA && currencyB && currencyA?.wrapped.sortsBefore(currencyB?.wrapped)
|
||||
|
||||
const leftPrice = isSorted ? priceLower : priceUpper?.invert()
|
||||
const rightPrice = isSorted ? priceUpper : priceLower?.invert()
|
||||
|
||||
return leftPrice && rightPrice
|
||||
? [parseFloat(leftPrice?.toSignificant(5)), parseFloat(rightPrice?.toSignificant(5))]
|
||||
: undefined
|
||||
}, [currencyA, currencyB, priceLower, priceUpper])
|
||||
|
||||
const brushLabelValue = useCallback(
|
||||
(d: 'w' | 'e', x: number) => {
|
||||
if (!price) return ''
|
||||
|
||||
if (d === 'w' && ticksAtLimit[Bound.LOWER]) return '0'
|
||||
if (d === 'e' && ticksAtLimit[Bound.UPPER]) return '∞'
|
||||
|
||||
//const percent = (((x < price ? -1 : 1) * (Math.max(x, price) - Math.min(x, price))) / Math.min(x, price)) * 100
|
||||
|
||||
const percent = (x < price ? -1 : 1) * ((Math.max(x, price) - Math.min(x, price)) / price) * 100
|
||||
|
||||
return price ? `${format(Math.abs(percent) > 1 ? '.2~s' : '.2~f')(percent)}%` : ''
|
||||
},
|
||||
[price, ticksAtLimit]
|
||||
)
|
||||
|
||||
if (isError) {
|
||||
ReactGA.exception({
|
||||
...error,
|
||||
category: 'Liquidity',
|
||||
fatal: false,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<AutoColumn gap="md" style={{ minHeight: '200px' }}>
|
||||
{isUninitialized ? (
|
||||
<InfoBox
|
||||
message={<Trans>Your position will appear here.</Trans>}
|
||||
icon={<Inbox size={56} stroke={theme.text1} />}
|
||||
/>
|
||||
) : isLoading ? (
|
||||
<InfoBox icon={<Loader size="40px" stroke={theme.text4} />} />
|
||||
) : isError ? (
|
||||
<InfoBox
|
||||
message={<Trans>Liquidity data not available.</Trans>}
|
||||
icon={<CloudOff size={56} stroke={theme.text4} />}
|
||||
/>
|
||||
) : !formattedData || formattedData === [] || !price ? (
|
||||
<InfoBox
|
||||
message={<Trans>There is no liquidity data.</Trans>}
|
||||
icon={<BarChart2 size={56} stroke={theme.text4} />}
|
||||
/>
|
||||
) : (
|
||||
<ChartWrapper>
|
||||
<Chart
|
||||
data={{ series: formattedData, current: price }}
|
||||
dimensions={{ width: 400, height: 200 }}
|
||||
margins={{ top: 10, right: 2, bottom: 20, left: 0 }}
|
||||
styles={{
|
||||
area: {
|
||||
selection: theme.blue1,
|
||||
},
|
||||
brush: {
|
||||
handle: {
|
||||
west: saturate(0.1, tokenAColor) ?? theme.red1,
|
||||
east: saturate(0.1, tokenBColor) ?? theme.blue1,
|
||||
},
|
||||
},
|
||||
}}
|
||||
interactive={interactive}
|
||||
brushLabels={brushLabelValue}
|
||||
brushDomain={brushDomain}
|
||||
onBrushDomainChange={onBrushDomainChangeEnded}
|
||||
zoomLevels={ZOOM_LEVELS[feeAmount ?? FeeAmount.MEDIUM]}
|
||||
/>
|
||||
</ChartWrapper>
|
||||
)}
|
||||
</AutoColumn>
|
||||
)
|
||||
}
|
||||
42
src/components/LiquidityChartRangeInput/svg.ts
Normal file
42
src/components/LiquidityChartRangeInput/svg.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Generates an SVG path for the east brush handle.
|
||||
* Apply `scale(-1, 1)` to generate west brush handle.
|
||||
*
|
||||
* |```````\
|
||||
* | | | |
|
||||
* |______/
|
||||
* |
|
||||
* |
|
||||
* |
|
||||
* |
|
||||
* |
|
||||
*
|
||||
* https://medium.com/@dennismphil/one-side-rounded-rectangle-using-svg-fb31cf318d90
|
||||
*/
|
||||
export const brushHandlePath = (height: number) =>
|
||||
[
|
||||
// handle
|
||||
`M 0 0`, // move to origin
|
||||
`v ${height}`, // vertical line
|
||||
'm 1 0', // move 1px to the right
|
||||
`V 0`, // second vertical line
|
||||
`M 0 2`, // move to origin
|
||||
|
||||
// head
|
||||
'h 12', // horizontal line
|
||||
'q 2 0, 2 2', // rounded corner
|
||||
'v 22', // vertical line
|
||||
'q 0 2 -2 2', // rounded corner
|
||||
'h -12', // horizontal line
|
||||
`z`, // close path
|
||||
].join(' ')
|
||||
|
||||
export const brushHandleAccentPath = () =>
|
||||
[
|
||||
'm 6 8', // move to first accent
|
||||
'v 14', // vertical line
|
||||
'M 0 0', // move to origin
|
||||
'm 10 8', // move to second accent
|
||||
'v 14', // vertical line
|
||||
'z',
|
||||
].join(' ')
|
||||
58
src/components/LiquidityChartRangeInput/types.ts
Normal file
58
src/components/LiquidityChartRangeInput/types.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
export interface ChartEntry {
|
||||
activeLiquidity: number
|
||||
price0: number
|
||||
}
|
||||
|
||||
export interface Dimensions {
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
export interface Margins {
|
||||
top: number
|
||||
right: number
|
||||
bottom: number
|
||||
left: number
|
||||
}
|
||||
|
||||
export interface ZoomLevels {
|
||||
initialMin: number
|
||||
initialMax: number
|
||||
min: number
|
||||
max: number
|
||||
}
|
||||
|
||||
export interface LiquidityChartRangeInputProps {
|
||||
// to distringuish between multiple charts in the DOM
|
||||
id?: string
|
||||
|
||||
data: {
|
||||
series: ChartEntry[]
|
||||
current: number
|
||||
}
|
||||
|
||||
styles: {
|
||||
area: {
|
||||
// color of the ticks in range
|
||||
selection: string
|
||||
}
|
||||
|
||||
brush: {
|
||||
handle: {
|
||||
west: string
|
||||
east: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dimensions: Dimensions
|
||||
margins: Margins
|
||||
|
||||
interactive?: boolean
|
||||
|
||||
brushLabels: (d: 'w' | 'e', x: number) => string
|
||||
brushDomain: [number, number] | undefined
|
||||
onBrushDomainChange: (domain: [number, number]) => void
|
||||
|
||||
zoomLevels: ZoomLevels
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import styled, { keyframes } from 'styled-components'
|
||||
import styled, { keyframes } from 'styled-components/macro'
|
||||
|
||||
const rotate = keyframes`
|
||||
from {
|
||||
|
||||
@@ -5,7 +5,7 @@ import useTheme from '../../hooks/useTheme'
|
||||
|
||||
const BAD_SRCS: { [tokenAddress: string]: true } = {}
|
||||
|
||||
export interface LogoProps extends Pick<ImageProps, 'style' | 'alt' | 'className'> {
|
||||
interface LogoProps extends Pick<ImageProps, 'style' | 'alt' | 'className'> {
|
||||
srcs: string[]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import React, { useRef } from 'react'
|
||||
import { BookOpen, Code, Info, MessageCircle, PieChart } from 'react-feather'
|
||||
import { BookOpen, Code, Info, MessageCircle, PieChart, Moon, Sun } from 'react-feather'
|
||||
import { Link } from 'react-router-dom'
|
||||
import styled, { css } from 'styled-components'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
import { ReactComponent as MenuIcon } from '../../assets/images/menu.svg'
|
||||
import { useActiveWeb3React } from '../../hooks/web3'
|
||||
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
|
||||
import { ApplicationModal } from '../../state/application/actions'
|
||||
import { useModalOpen, useToggleModal } from '../../state/application/hooks'
|
||||
import { Trans } from '@lingui/macro'
|
||||
|
||||
import { ExternalLink } from '../../theme'
|
||||
import { ButtonPrimary } from '../Button'
|
||||
import { useDarkModeManager } from 'state/user/hooks'
|
||||
|
||||
import { L2_CHAIN_IDS, CHAIN_INFO, SupportedChainId } from 'constants/chains'
|
||||
|
||||
export enum FlyoutAlignment {
|
||||
LEFT = 'LEFT',
|
||||
@@ -30,17 +32,18 @@ const StyledMenuButton = styled.button`
|
||||
background-color: transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 35px;
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
height: 38px;
|
||||
background-color: ${({ theme }) => theme.bg0};
|
||||
border: 1px solid ${({ theme }) => theme.bg0};
|
||||
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
border-radius: 12px;
|
||||
|
||||
:hover,
|
||||
:focus {
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
background-color: ${({ theme }) => theme.bg3};
|
||||
border: 1px solid ${({ theme }) => theme.bg3};
|
||||
}
|
||||
|
||||
svg {
|
||||
@@ -65,18 +68,20 @@ const StyledMenu = styled.div`
|
||||
`
|
||||
|
||||
const MenuFlyout = styled.span<{ flyoutAlignment?: FlyoutAlignment }>`
|
||||
min-width: 8.125rem;
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
min-width: 196px;
|
||||
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.bg0};
|
||||
border-radius: 12px;
|
||||
padding: 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 1rem;
|
||||
font-size: 16px;
|
||||
position: absolute;
|
||||
top: 3rem;
|
||||
z-index: 100;
|
||||
|
||||
${({ flyoutAlignment = FlyoutAlignment.RIGHT }) =>
|
||||
flyoutAlignment === FlyoutAlignment.RIGHT
|
||||
? css`
|
||||
@@ -86,7 +91,9 @@ const MenuFlyout = styled.span<{ flyoutAlignment?: FlyoutAlignment }>`
|
||||
left: 0rem;
|
||||
`};
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
top: -17.25rem;
|
||||
bottom: unset;
|
||||
right: 0;
|
||||
left: unset;
|
||||
`};
|
||||
`
|
||||
|
||||
@@ -96,15 +103,13 @@ const MenuItem = styled(ExternalLink)`
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0.5rem 0.5rem;
|
||||
justify-content: space-between;
|
||||
color: ${({ theme }) => theme.text2};
|
||||
:hover {
|
||||
color: ${({ theme }) => theme.text1};
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
> svg {
|
||||
margin-right: 8px;
|
||||
}
|
||||
`
|
||||
|
||||
const InternalMenuItem = styled(Link)`
|
||||
@@ -121,16 +126,41 @@ const InternalMenuItem = styled(Link)`
|
||||
}
|
||||
`
|
||||
|
||||
const ToggleMenuItem = styled.button`
|
||||
background-color: transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0.5rem 0.5rem;
|
||||
justify-content: space-between;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
color: ${({ theme }) => theme.text2};
|
||||
:hover {
|
||||
color: ${({ theme }) => theme.text1};
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
`
|
||||
|
||||
const CODE_LINK = 'https://github.com/Uniswap/uniswap-interface'
|
||||
|
||||
export default function Menu() {
|
||||
const { account } = useActiveWeb3React()
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
|
||||
const node = useRef<HTMLDivElement>()
|
||||
const open = useModalOpen(ApplicationModal.MENU)
|
||||
const toggle = useToggleModal(ApplicationModal.MENU)
|
||||
useOnClickOutside(node, open ? toggle : undefined)
|
||||
const openClaimModal = useToggleModal(ApplicationModal.ADDRESS_CLAIM)
|
||||
const showUNIClaimOption = Boolean(!!account && !!chainId && !L2_CHAIN_IDS.includes(chainId))
|
||||
const { infoLink } = CHAIN_INFO[chainId ? chainId : SupportedChainId.MAINNET]
|
||||
|
||||
const [darkMode, toggleDarkMode] = useDarkModeManager()
|
||||
|
||||
return (
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30451
|
||||
@@ -142,37 +172,41 @@ export default function Menu() {
|
||||
{open && (
|
||||
<MenuFlyout>
|
||||
<MenuItem href="https://uniswap.org/">
|
||||
<Info size={14} />
|
||||
<div>
|
||||
<Trans>About</Trans>
|
||||
</div>
|
||||
<Info opacity={0.6} size={16} />
|
||||
</MenuItem>
|
||||
<MenuItem href="https://docs.uniswap.org/">
|
||||
<BookOpen size={14} />
|
||||
<div>
|
||||
<Trans>Docs</Trans>
|
||||
</div>
|
||||
<BookOpen opacity={0.6} size={16} />
|
||||
</MenuItem>
|
||||
<MenuItem href={CODE_LINK}>
|
||||
<Code size={14} />
|
||||
<div>
|
||||
<Trans>Code</Trans>
|
||||
</div>
|
||||
<Code opacity={0.6} size={16} />
|
||||
</MenuItem>
|
||||
<MenuItem href="https://discord.gg/FCfyBSbCU5">
|
||||
<MessageCircle size={14} />
|
||||
<div>
|
||||
<Trans>Discord</Trans>
|
||||
</div>
|
||||
<MessageCircle opacity={0.6} size={16} />
|
||||
</MenuItem>
|
||||
<MenuItem href="https://info.uniswap.org/">
|
||||
<PieChart size={14} />
|
||||
<MenuItem href={infoLink}>
|
||||
<div>
|
||||
<Trans>Analytics</Trans>
|
||||
</div>
|
||||
<PieChart opacity={0.6} size={16} />
|
||||
</MenuItem>
|
||||
{account && (
|
||||
<UNIbutton onClick={openClaimModal} padding="8px 16px" width="100%" borderRadius="12px" mt="0.5rem">
|
||||
<ToggleMenuItem onClick={() => toggleDarkMode()}>
|
||||
<div>{darkMode ? <Trans>Light Theme</Trans> : <Trans>Dark Theme</Trans>}</div>
|
||||
{darkMode ? <Moon opacity={0.6} size={16} /> : <Sun opacity={0.6} size={16} />}
|
||||
</ToggleMenuItem>
|
||||
{showUNIClaimOption && (
|
||||
<UNIbutton onClick={openClaimModal} padding="8px 16px" width="100%" $borderRadius="12px" mt="0.5rem">
|
||||
<Trans>Claim UNI</Trans>
|
||||
</UNIbutton>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import styled, { css } from 'styled-components'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
import { animated, useTransition, useSpring } from 'react-spring'
|
||||
import { DialogOverlay, DialogContent } from '@reach/dialog'
|
||||
import { isMobile } from 'react-device-detect'
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useActiveWeb3React } from '../../hooks/web3'
|
||||
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
|
||||
|
||||
import { AutoColumn, ColumnCenter } from '../Column'
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
import { RowBetween } from '../Row'
|
||||
import { TYPE, CloseIcon, CustomLightSpinner } from '../../theme'
|
||||
import { ArrowUpCircle } from 'react-feather'
|
||||
|
||||
@@ -13,6 +13,8 @@ import { resetMintState } from 'state/mint/actions'
|
||||
import { resetMintState as resetMintV3State } from 'state/mint/v3/actions'
|
||||
import { TYPE } from 'theme'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import { ReactNode } from 'react'
|
||||
import { Box } from 'rebass'
|
||||
|
||||
const Tabs = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
@@ -49,6 +51,15 @@ const StyledNavLink = styled(NavLink).attrs({
|
||||
}
|
||||
`
|
||||
|
||||
const StyledHistoryLink = styled(HistoryLink)<{ flex: string | undefined }>`
|
||||
flex: ${({ flex }) => flex ?? 'none'};
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
flex: none;
|
||||
margin-right: 10px;
|
||||
`};
|
||||
`
|
||||
|
||||
const ActiveText = styled.div`
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
@@ -91,11 +102,14 @@ export function AddRemoveTabs({
|
||||
creating,
|
||||
defaultSlippage,
|
||||
positionID,
|
||||
children,
|
||||
}: {
|
||||
adding: boolean
|
||||
creating: boolean
|
||||
defaultSlippage: Percent
|
||||
positionID?: string | undefined
|
||||
showBackLink?: boolean
|
||||
children?: ReactNode | undefined
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
// reset states on back
|
||||
@@ -110,7 +124,7 @@ export function AddRemoveTabs({
|
||||
return (
|
||||
<Tabs>
|
||||
<RowBetween style={{ padding: '1rem 1rem 0 1rem' }}>
|
||||
<HistoryLink
|
||||
<StyledHistoryLink
|
||||
to={poolLink}
|
||||
onClick={() => {
|
||||
if (adding) {
|
||||
@@ -119,10 +133,15 @@ export function AddRemoveTabs({
|
||||
dispatch(resetMintV3State())
|
||||
}
|
||||
}}
|
||||
flex={children ? '1' : undefined}
|
||||
>
|
||||
<StyledArrowLeft stroke={theme.text2} />
|
||||
</HistoryLink>
|
||||
<TYPE.mediumHeader fontWeight={500} fontSize={20}>
|
||||
</StyledHistoryLink>
|
||||
<TYPE.mediumHeader
|
||||
fontWeight={500}
|
||||
fontSize={20}
|
||||
style={{ flex: '1', margin: 'auto', textAlign: children ? 'start' : 'center' }}
|
||||
>
|
||||
{creating ? (
|
||||
<Trans>Create a pair</Trans>
|
||||
) : adding ? (
|
||||
@@ -131,6 +150,7 @@ export function AddRemoveTabs({
|
||||
<Trans>Remove Liquidity</Trans>
|
||||
)}
|
||||
</TYPE.mediumHeader>
|
||||
<Box style={{ marginRight: '.5rem' }}>{children}</Box>
|
||||
<SettingsTab placeholderSlippage={defaultSlippage} />
|
||||
</RowBetween>
|
||||
</Tabs>
|
||||
|
||||
128
src/components/NetworkAlert/AddLiquidityNetworkAlert.tsx
Normal file
128
src/components/NetworkAlert/AddLiquidityNetworkAlert.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import {
|
||||
ArbitrumWrapperBackgroundDarkMode,
|
||||
ArbitrumWrapperBackgroundLightMode,
|
||||
OptimismWrapperBackgroundDarkMode,
|
||||
OptimismWrapperBackgroundLightMode,
|
||||
} from 'components/NetworkAlert/NetworkAlert'
|
||||
import { CHAIN_INFO, L2_CHAIN_IDS, NETWORK_LABELS, SupportedChainId, SupportedL2ChainId } from 'constants/chains'
|
||||
import { useActiveWeb3React } from 'hooks/web3'
|
||||
import { ArrowDownCircle } from 'react-feather'
|
||||
import { useArbitrumAlphaAlert, useDarkModeManager } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
|
||||
|
||||
const L2Icon = styled.img`
|
||||
display: none;
|
||||
height: 40px;
|
||||
margin: auto 20px auto 4px;
|
||||
width: 40px;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
|
||||
display: block;
|
||||
}
|
||||
`
|
||||
const DesktopTextBreak = styled.div`
|
||||
display: none;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
|
||||
display: block;
|
||||
}
|
||||
`
|
||||
const Wrapper = styled.div<{ chainId: SupportedL2ChainId; darkMode: boolean; logoUrl: string }>`
|
||||
${({ chainId, darkMode }) =>
|
||||
[SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
|
||||
? darkMode
|
||||
? OptimismWrapperBackgroundDarkMode
|
||||
: OptimismWrapperBackgroundLightMode
|
||||
: darkMode
|
||||
? ArbitrumWrapperBackgroundDarkMode
|
||||
: ArbitrumWrapperBackgroundLightMode};
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 12px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
:before {
|
||||
background-image: url(${({ logoUrl }) => logoUrl});
|
||||
background-repeat: no-repeat;
|
||||
background-size: 300px;
|
||||
content: '';
|
||||
height: 300px;
|
||||
opacity: 0.1;
|
||||
position: absolute;
|
||||
transform: rotate(25deg) translate(-90px, -40px);
|
||||
width: 300px;
|
||||
z-index: -1;
|
||||
}
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
|
||||
flex-direction: row;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
`
|
||||
const Body = styled.div`
|
||||
font-size: 12px;
|
||||
line-height: 143%;
|
||||
margin: 12px;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
|
||||
flex: 1 1 auto;
|
||||
margin: auto 0;
|
||||
}
|
||||
`
|
||||
const LinkOutCircle = styled(ArrowDownCircle)`
|
||||
transform: rotate(230deg);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-left: 12px;
|
||||
`
|
||||
const LinkOutToBridge = styled(ExternalLink)`
|
||||
align-items: center;
|
||||
background-color: black;
|
||||
border-radius: 16px;
|
||||
color: white;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
justify-content: space-between;
|
||||
margin: 0;
|
||||
max-height: 47px;
|
||||
padding: 16px 12px;
|
||||
text-decoration: none;
|
||||
width: auto;
|
||||
:hover,
|
||||
:focus,
|
||||
:active {
|
||||
background-color: black;
|
||||
}
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
|
||||
margin: auto 0 auto auto;
|
||||
padding: 14px 16px;
|
||||
min-width: 226px;
|
||||
}
|
||||
`
|
||||
export function AddLiquidityNetworkAlert() {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const [darkMode] = useDarkModeManager()
|
||||
const [arbitrumAlphaAcknowledged] = useArbitrumAlphaAlert()
|
||||
|
||||
if (!chainId || !L2_CHAIN_IDS.includes(chainId) || arbitrumAlphaAcknowledged) {
|
||||
return null
|
||||
}
|
||||
const info = CHAIN_INFO[chainId as SupportedL2ChainId]
|
||||
const depositUrl = [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
|
||||
? `${info.bridge}?chainId=1`
|
||||
: info.bridge
|
||||
return (
|
||||
<Wrapper darkMode={darkMode} chainId={chainId} logoUrl={info.logoUrl}>
|
||||
<L2Icon src={info.logoUrl} />
|
||||
<Body>
|
||||
<Trans>This is an alpha release of Uniswap on the {NETWORK_LABELS[chainId]} network.</Trans>
|
||||
<DesktopTextBreak /> <Trans>You must bridge L1 assets to the network to use them.</Trans>
|
||||
</Body>
|
||||
<LinkOutToBridge href={depositUrl}>
|
||||
<Trans>Deposit to {NETWORK_LABELS[chainId]}</Trans>
|
||||
<LinkOutCircle />
|
||||
</LinkOutToBridge>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
128
src/components/NetworkAlert/MinimalNetworkAlert.tsx
Normal file
128
src/components/NetworkAlert/MinimalNetworkAlert.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import {
|
||||
ArbitrumWrapperBackgroundDarkMode,
|
||||
ArbitrumWrapperBackgroundLightMode,
|
||||
OptimismWrapperBackgroundDarkMode,
|
||||
OptimismWrapperBackgroundLightMode,
|
||||
} from 'components/NetworkAlert/NetworkAlert'
|
||||
import { CHAIN_INFO, L2_CHAIN_IDS, NETWORK_LABELS, SupportedChainId, SupportedL2ChainId } from 'constants/chains'
|
||||
import { useActiveWeb3React } from 'hooks/web3'
|
||||
import { ArrowDownCircle } from 'react-feather'
|
||||
import { useArbitrumAlphaAlert, useDarkModeManager } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
|
||||
|
||||
const L2Icon = styled.img`
|
||||
display: none;
|
||||
height: 40px;
|
||||
margin: auto 20px auto 4px;
|
||||
width: 40px;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
display: block;
|
||||
}
|
||||
`
|
||||
const DesktopTextBreak = styled.div`
|
||||
display: none;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
|
||||
display: block;
|
||||
}
|
||||
`
|
||||
const Wrapper = styled.div<{ chainId: SupportedL2ChainId; darkMode: boolean; logoUrl: string }>`
|
||||
${({ chainId, darkMode }) =>
|
||||
[SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
|
||||
? darkMode
|
||||
? OptimismWrapperBackgroundDarkMode
|
||||
: OptimismWrapperBackgroundLightMode
|
||||
: darkMode
|
||||
? ArbitrumWrapperBackgroundDarkMode
|
||||
: ArbitrumWrapperBackgroundLightMode};
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 12px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
:before {
|
||||
background-image: url(${({ logoUrl }) => logoUrl});
|
||||
background-repeat: no-repeat;
|
||||
background-size: 300px;
|
||||
content: '';
|
||||
height: 300px;
|
||||
opacity: 0.1;
|
||||
position: absolute;
|
||||
transform: rotate(25deg) translate(-90px, -40px);
|
||||
width: 300px;
|
||||
z-index: -1;
|
||||
}
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
flex-direction: row;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
`
|
||||
const Body = styled.div`
|
||||
font-size: 12px;
|
||||
line-height: 143%;
|
||||
margin: 12px;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
flex: 1 1 auto;
|
||||
margin: auto 0;
|
||||
}
|
||||
`
|
||||
const LinkOutCircle = styled(ArrowDownCircle)`
|
||||
transform: rotate(230deg);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-left: 12px;
|
||||
`
|
||||
const LinkOutToBridge = styled(ExternalLink)`
|
||||
align-items: center;
|
||||
background-color: black;
|
||||
border-radius: 16px;
|
||||
color: white;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
justify-content: space-between;
|
||||
margin: 0;
|
||||
max-height: 47px;
|
||||
padding: 16px 8px;
|
||||
text-decoration: none;
|
||||
width: auto;
|
||||
:hover,
|
||||
:focus,
|
||||
:active {
|
||||
background-color: black;
|
||||
}
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
margin: auto 0 auto auto;
|
||||
padding: 14px 17px;
|
||||
min-width: 226px;
|
||||
}
|
||||
`
|
||||
export function MinimalNetworkAlert() {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const [darkMode] = useDarkModeManager()
|
||||
const [arbitrumAlphaAcknowledged] = useArbitrumAlphaAlert()
|
||||
|
||||
if (!chainId || !L2_CHAIN_IDS.includes(chainId) || arbitrumAlphaAcknowledged) {
|
||||
return null
|
||||
}
|
||||
const info = CHAIN_INFO[chainId as SupportedL2ChainId]
|
||||
const depositUrl = [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
|
||||
? `${info.bridge}?chainId=1`
|
||||
: info.bridge
|
||||
return (
|
||||
<Wrapper darkMode={darkMode} chainId={chainId} logoUrl={info.logoUrl}>
|
||||
<L2Icon src={info.logoUrl} />
|
||||
<Body>
|
||||
<Trans>This is an alpha release of Uniswap on the {NETWORK_LABELS[chainId]} network.</Trans>
|
||||
<DesktopTextBreak /> <Trans>You must bridge L1 assets to the network to use them.</Trans>
|
||||
</Body>
|
||||
<LinkOutToBridge href={depositUrl}>
|
||||
<Trans>Deposit to {NETWORK_LABELS[chainId]}</Trans>
|
||||
<LinkOutCircle />
|
||||
</LinkOutToBridge>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
163
src/components/NetworkAlert/NetworkAlert.tsx
Normal file
163
src/components/NetworkAlert/NetworkAlert.tsx
Normal file
@@ -0,0 +1,163 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { L2_CHAIN_IDS, NETWORK_LABELS, SupportedChainId, SupportedL2ChainId } from 'constants/chains'
|
||||
import { useActiveWeb3React } from 'hooks/web3'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { ArrowDownCircle, X } from 'react-feather'
|
||||
import { useArbitrumAlphaAlert, useDarkModeManager } from 'state/user/hooks'
|
||||
import { useETHBalances } from 'state/wallet/hooks'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
|
||||
import { CHAIN_INFO } from '../../constants/chains'
|
||||
|
||||
const L2Icon = styled.img`
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
justify-self: center;
|
||||
`
|
||||
const CloseIcon = styled(X)`
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
`
|
||||
const ContentWrapper = styled.div`
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-gap: 4px;
|
||||
grid-template-columns: 40px 4fr;
|
||||
grid-template-rows: auto auto;
|
||||
margin: 20px 16px;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
grid-template-columns: 42px 4fr;
|
||||
grid-gap: 8px;
|
||||
}
|
||||
`
|
||||
export const ArbitrumWrapperBackgroundDarkMode = css`
|
||||
background: radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.1) 0%, rgba(219, 255, 0, 0) 100%),
|
||||
radial-gradient(75% 75% at 0% 0%, rgba(150, 190, 220, 0.3) 0%, rgba(33, 114, 229, 0.3) 100%), hsla(0, 0%, 100%, 0.1);
|
||||
`
|
||||
export const ArbitrumWrapperBackgroundLightMode = css`
|
||||
background: radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.1) 0%, rgba(219, 255, 0, 0) 100%),
|
||||
radial-gradient(circle at top left, hsla(206, 50%, 75%, 0.01), hsla(215, 79%, 51%, 0.12)), hsla(0, 0%, 100%, 0.1);
|
||||
`
|
||||
export const OptimismWrapperBackgroundDarkMode = css`
|
||||
background: radial-gradient(948% 292% at 42% 0%, rgba(255, 58, 212, 0.2) 0%, rgba(255, 255, 255, 0.1) 100%),
|
||||
radial-gradient(98% 96% at 2% 0%, rgba(255, 39, 39, 0.5) 0%, rgba(235, 0, 255, 0.345) 96%);
|
||||
`
|
||||
export const OptimismWrapperBackgroundLightMode = css`
|
||||
background: radial-gradient(92% 105% at 50% 7%, rgba(255, 58, 212, 0.04) 0%, rgba(255, 255, 255, 0.03) 100%),
|
||||
radial-gradient(100% 97% at 0% 12%, rgba(235, 0, 255, 0.1) 0%, rgba(243, 19, 19, 0.1) 100%), hsla(0, 0%, 100%, 0.5);
|
||||
`
|
||||
const RootWrapper = styled.div<{ chainId: SupportedChainId; darkMode: boolean; logoUrl: string }>`
|
||||
${({ chainId, darkMode }) =>
|
||||
[SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
|
||||
? darkMode
|
||||
? OptimismWrapperBackgroundDarkMode
|
||||
: OptimismWrapperBackgroundLightMode
|
||||
: darkMode
|
||||
? ArbitrumWrapperBackgroundDarkMode
|
||||
: ArbitrumWrapperBackgroundLightMode};
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 480px;
|
||||
min-height: 174px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
:before {
|
||||
background-image: url(${({ logoUrl }) => logoUrl});
|
||||
background-repeat: no-repeat;
|
||||
background-size: 300px;
|
||||
content: '';
|
||||
height: 300px;
|
||||
opacity: 0.1;
|
||||
position: absolute;
|
||||
transform: rotate(25deg) translate(-90px, -40px);
|
||||
width: 300px;
|
||||
z-index: -1;
|
||||
}
|
||||
`
|
||||
const Header = styled.h2`
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
margin: 0;
|
||||
padding-right: 30px;
|
||||
`
|
||||
const Body = styled.p`
|
||||
font-size: 12px;
|
||||
grid-column: 1 / 3;
|
||||
line-height: 143%;
|
||||
margin: 0;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
grid-column: 2 / 3;
|
||||
}
|
||||
`
|
||||
const LinkOutCircle = styled(ArrowDownCircle)`
|
||||
transform: rotate(230deg);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
`
|
||||
const LinkOutToBridge = styled(ExternalLink)`
|
||||
align-items: center;
|
||||
background-color: black;
|
||||
border-radius: 16px;
|
||||
color: white;
|
||||
display: flex;
|
||||
font-size: 16px;
|
||||
height: 44px;
|
||||
justify-content: space-between;
|
||||
margin: 0 20px 20px 20px;
|
||||
padding: 12px 16px;
|
||||
text-decoration: none;
|
||||
width: auto;
|
||||
:hover,
|
||||
:focus,
|
||||
:active {
|
||||
background-color: black;
|
||||
}
|
||||
`
|
||||
export function NetworkAlert() {
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
const [darkMode] = useDarkModeManager()
|
||||
const [arbitrumAlphaAcknowledged, setArbitrumAlphaAcknowledged] = useArbitrumAlphaAlert()
|
||||
const [locallyDismissed, setLocallyDimissed] = useState(false)
|
||||
const userEthBalance = useETHBalances(account ? [account] : [])?.[account ?? '']
|
||||
|
||||
const dismiss = useCallback(() => {
|
||||
if (userEthBalance?.greaterThan(0)) {
|
||||
setArbitrumAlphaAcknowledged(true)
|
||||
} else {
|
||||
setLocallyDimissed(true)
|
||||
}
|
||||
}, [setArbitrumAlphaAcknowledged, userEthBalance])
|
||||
if (!chainId || !L2_CHAIN_IDS.includes(chainId) || arbitrumAlphaAcknowledged || locallyDismissed) {
|
||||
return null
|
||||
}
|
||||
const info = CHAIN_INFO[chainId as SupportedL2ChainId]
|
||||
const depositUrl = [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
|
||||
? `${info.bridge}?chainId=1`
|
||||
: info.bridge
|
||||
return (
|
||||
<RootWrapper chainId={chainId} darkMode={darkMode} logoUrl={info.logoUrl}>
|
||||
<CloseIcon onClick={dismiss} />
|
||||
<ContentWrapper>
|
||||
<L2Icon src={info.logoUrl} />
|
||||
<Header>
|
||||
<Trans>Uniswap on {NETWORK_LABELS[chainId]}</Trans>
|
||||
</Header>
|
||||
<Body>
|
||||
<Trans>
|
||||
This is an alpha release of Uniswap on the {NETWORK_LABELS[chainId]} network. You must bridge L1 assets to
|
||||
the network to swap them.
|
||||
</Trans>
|
||||
</Body>
|
||||
</ContentWrapper>
|
||||
<LinkOutToBridge href={depositUrl}>
|
||||
<Trans>Deposit to {NETWORK_LABELS[chainId]}</Trans>
|
||||
<LinkOutCircle />
|
||||
</LinkOutToBridge>
|
||||
</RootWrapper>
|
||||
)
|
||||
}
|
||||
63
src/components/OptimismDowntimeWarning/index.tsx
Normal file
63
src/components/OptimismDowntimeWarning/index.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { useActiveWeb3React } from 'hooks/web3'
|
||||
import { AlertOctagon } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const Root = styled.div`
|
||||
background-color: ${({ theme }) => theme.yellow3};
|
||||
border-radius: 18px;
|
||||
color: black;
|
||||
margin-top: 16px;
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
`
|
||||
const WarningIcon = styled(AlertOctagon)`
|
||||
margin: 0 8px 0 0;
|
||||
`
|
||||
const TitleRow = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
line-height: 25px;
|
||||
`
|
||||
const Body = styled.div`
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
margin: 8px 0 0 0;
|
||||
`
|
||||
const LinkOutToNotion = styled.a`
|
||||
color: black;
|
||||
`
|
||||
|
||||
export default function OptimismDowntimeWarning() {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
if (!chainId || ![SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Root>
|
||||
<TitleRow>
|
||||
<WarningIcon />
|
||||
<Trans>{'Optimism'} Scheduled Downtimes</Trans>
|
||||
</TitleRow>
|
||||
<Body>
|
||||
<Trans>
|
||||
{'Optimism'} expects some scheduled downtime in the near future.
|
||||
<LinkOutToNotion
|
||||
href={`https://www.notion.so/Optimism-Regenesis-Schedule-8d14a34902ca4f5a8910762b3ec4b8da`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Read more.
|
||||
</LinkOutToNotion>
|
||||
</Trans>
|
||||
</Body>
|
||||
</Root>
|
||||
)
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import ReactGA from 'react-ga'
|
||||
import { Heart, X } from 'react-feather'
|
||||
import styled, { keyframes } from 'styled-components'
|
||||
import styled, { keyframes } from 'styled-components/macro'
|
||||
import tokenLogo from '../../assets/images/token-logo.png'
|
||||
import { ButtonPrimary } from '../../components/Button'
|
||||
import { useActiveWeb3React } from '../../hooks/web3'
|
||||
@@ -117,7 +117,7 @@ export default function ClaimPopup() {
|
||||
</TYPE.subHeader>
|
||||
</AutoColumn>
|
||||
<AutoColumn style={{ zIndex: 10 }} justify="center">
|
||||
<ButtonPrimary padding="8px" borderRadius="8px" width={'fit-content'} onClick={handleToggleSelfClaimModal}>
|
||||
<ButtonPrimary padding="8px" $borderRadius="8px" width={'fit-content'} onClick={handleToggleSelfClaimModal}>
|
||||
<Trans>Claim your UNI tokens</Trans>
|
||||
</ButtonPrimary>
|
||||
</AutoColumn>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useCallback, useContext, useEffect } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import { useSpring } from 'react-spring/web'
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
import { animated } from 'react-spring'
|
||||
import { PopupContent } from '../../state/application/actions'
|
||||
import { useRemovePopup } from '../../state/application/hooks'
|
||||
import TransactionPopup from './TransactionPopup'
|
||||
|
||||
export const StyledClose = styled(X)`
|
||||
const StyledClose = styled(X)`
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
@@ -16,7 +16,7 @@ export const StyledClose = styled(X)`
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
export const Popup = styled.div`
|
||||
const Popup = styled.div`
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
padding: 1em;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useContext } from 'react'
|
||||
import { AlertCircle, CheckCircle } from 'react-feather'
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
import { useActiveWeb3React } from '../../hooks/web3'
|
||||
import { TYPE } from '../../theme'
|
||||
import { ExternalLink } from '../../theme/components'
|
||||
|
||||
@@ -4,6 +4,9 @@ import { AutoColumn } from '../Column'
|
||||
import PopupItem from './PopupItem'
|
||||
import ClaimPopup from './ClaimPopup'
|
||||
import { useURLWarningVisible } from '../../state/user/hooks'
|
||||
import { useActiveWeb3React } from 'hooks/web3'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { MEDIA_WIDTHS } from 'theme'
|
||||
|
||||
const MobilePopupWrapper = styled.div<{ height: string | number }>`
|
||||
position: relative;
|
||||
@@ -30,9 +33,13 @@ const MobilePopupInner = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean }>`
|
||||
const StopOverflowQuery = `@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium + 1}px) and (max-width: ${
|
||||
MEDIA_WIDTHS.upToMedium + 500
|
||||
}px)`
|
||||
|
||||
const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean; xlPadding: boolean }>`
|
||||
position: fixed;
|
||||
top: ${({ extraPadding }) => (extraPadding ? '80px' : '88px')};
|
||||
top: ${({ extraPadding }) => (extraPadding ? '64px' : '56px')};
|
||||
right: 1rem;
|
||||
max-width: 355px !important;
|
||||
width: 100%;
|
||||
@@ -41,6 +48,10 @@ const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean }>`
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
display: none;
|
||||
`};
|
||||
|
||||
${StopOverflowQuery} {
|
||||
top: ${({ extraPadding, xlPadding }) => (xlPadding ? '64px' : extraPadding ? '64px' : '56px')};
|
||||
}
|
||||
`
|
||||
|
||||
export default function Popups() {
|
||||
@@ -49,9 +60,13 @@ export default function Popups() {
|
||||
|
||||
const urlWarningActive = useURLWarningVisible()
|
||||
|
||||
// need extra padding if network is not L1 Ethereum
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const isNotOnMainnet = Boolean(chainId && chainId !== SupportedChainId.MAINNET)
|
||||
|
||||
return (
|
||||
<>
|
||||
<FixedPopupColumn gap="20px" extraPadding={urlWarningActive}>
|
||||
<FixedPopupColumn gap="20px" extraPadding={urlWarningActive} xlPadding={isNotOnMainnet}>
|
||||
<ClaimPopup />
|
||||
{activePopups.map((item) => (
|
||||
<PopupItem key={item.key} content={item.content} popKey={item.key} removeAfterMs={item.removeAfterMs} />
|
||||
|
||||
@@ -62,7 +62,7 @@ export default function SushiPositionCard({ tokenA, tokenB, liquidityToken, bord
|
||||
<RowFixed gap="8px">
|
||||
<ButtonEmpty
|
||||
padding="0px 35px 0px 0px"
|
||||
borderRadius="12px"
|
||||
$borderRadius="12px"
|
||||
width="fit-content"
|
||||
as={Link}
|
||||
to={`/migrate/v2/${liquidityToken.address}`}
|
||||
|
||||
@@ -98,7 +98,7 @@ export default function V2PositionCard({ pair, border, stakedBalance }: Position
|
||||
<RowFixed gap="8px">
|
||||
<ButtonEmpty
|
||||
padding="6px 8px"
|
||||
borderRadius="12px"
|
||||
$borderRadius="12px"
|
||||
width="fit-content"
|
||||
onClick={() => setShowMore(!showMore)}
|
||||
>
|
||||
@@ -188,7 +188,7 @@ export default function V2PositionCard({ pair, border, stakedBalance }: Position
|
||||
<RowBetween marginTop="10px">
|
||||
<ButtonPrimary
|
||||
padding="8px"
|
||||
borderRadius="8px"
|
||||
$borderRadius="8px"
|
||||
as={Link}
|
||||
to={`/migrate/v2/${pair.liquidityToken.address}`}
|
||||
width="64%"
|
||||
@@ -197,7 +197,7 @@ export default function V2PositionCard({ pair, border, stakedBalance }: Position
|
||||
</ButtonPrimary>
|
||||
<ButtonSecondary
|
||||
padding="8px"
|
||||
borderRadius="8px"
|
||||
$borderRadius="8px"
|
||||
as={Link}
|
||||
width="32%"
|
||||
to={`/remove/v2/${currencyId(currency0)}/${currencyId(currency1)}`}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import JSBI from 'jsbi'
|
||||
import { Percent, CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { darken } from 'polished'
|
||||
import { useState } from 'react'
|
||||
import { ChevronDown, ChevronUp } from 'react-feather'
|
||||
import { Link } from 'react-router-dom'
|
||||
@@ -21,7 +20,7 @@ import { CardNoise } from '../earn/styled'
|
||||
|
||||
import { useColor } from '../../hooks/useColor'
|
||||
|
||||
import Card, { GreyCard, LightCard } from '../Card'
|
||||
import { GreyCard, LightCard } from '../Card'
|
||||
import { AutoColumn } from '../Column'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import DoubleCurrencyLogo from '../DoubleLogo'
|
||||
@@ -33,12 +32,6 @@ export const FixedHeightRow = styled(RowBetween)`
|
||||
height: 24px;
|
||||
`
|
||||
|
||||
export const HoverCard = styled(Card)`
|
||||
border: 1px solid transparent;
|
||||
:hover {
|
||||
border: 1px solid ${({ theme }) => darken(0.06, theme.bg2)};
|
||||
}
|
||||
`
|
||||
const StyledPositionCard = styled(LightCard)<{ bgColor: any }>`
|
||||
border: none;
|
||||
background: ${({ theme, bgColor }) =>
|
||||
@@ -219,7 +212,7 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
|
||||
</Text>
|
||||
</AutoRow>
|
||||
<RowFixed gap="8px" style={{ marginRight: '4px' }}>
|
||||
<ButtonEmpty padding="6px 8px" borderRadius="12px" width="100%" onClick={() => setShowMore(!showMore)}>
|
||||
<ButtonEmpty padding="6px 8px" $borderRadius="12px" width="100%" onClick={() => setShowMore(!showMore)}>
|
||||
{showMore ? (
|
||||
<>
|
||||
<Trans>Manage</Trans>
|
||||
@@ -306,7 +299,7 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
|
||||
</Text>
|
||||
</FixedHeightRow>
|
||||
|
||||
<ButtonSecondary padding="8px" borderRadius="8px">
|
||||
<ButtonSecondary padding="8px" $borderRadius="8px">
|
||||
<ExternalLink
|
||||
style={{ width: '100%', textAlign: 'center' }}
|
||||
href={`https://v2.info.uniswap.org/account/${account}`}
|
||||
@@ -320,7 +313,7 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
|
||||
<RowBetween marginTop="10px">
|
||||
<ButtonPrimary
|
||||
padding="8px"
|
||||
borderRadius="8px"
|
||||
$borderRadius="8px"
|
||||
as={Link}
|
||||
to={`/migrate/v2/${pair.liquidityToken.address}`}
|
||||
width="32%"
|
||||
@@ -329,7 +322,7 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
|
||||
</ButtonPrimary>
|
||||
<ButtonPrimary
|
||||
padding="8px"
|
||||
borderRadius="8px"
|
||||
$borderRadius="8px"
|
||||
as={Link}
|
||||
to={`/add/v2/${currencyId(currency0)}/${currencyId(currency1)}`}
|
||||
width="32%"
|
||||
@@ -338,7 +331,7 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
|
||||
</ButtonPrimary>
|
||||
<ButtonPrimary
|
||||
padding="8px"
|
||||
borderRadius="8px"
|
||||
$borderRadius="8px"
|
||||
as={Link}
|
||||
width="32%"
|
||||
to={`/remove/v2/${currencyId(currency0)}/${currencyId(currency1)}`}
|
||||
@@ -350,7 +343,7 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
|
||||
{stakedBalance && JSBI.greaterThan(stakedBalance.quotient, BIG_INT_ZERO) && (
|
||||
<ButtonPrimary
|
||||
padding="8px"
|
||||
borderRadius="8px"
|
||||
$borderRadius="8px"
|
||||
as={Link}
|
||||
to={`/uni/${currencyId(currency0)}/${currencyId(currency1)}`}
|
||||
width="100%"
|
||||
|
||||
@@ -34,7 +34,7 @@ const MobileHeader = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
export type PositionListProps = React.PropsWithChildren<{
|
||||
type PositionListProps = React.PropsWithChildren<{
|
||||
positions: PositionDetails[]
|
||||
}>
|
||||
|
||||
@@ -47,7 +47,7 @@ export default function PositionList({ positions }: PositionListProps) {
|
||||
{positions && ' (' + positions.length + ')'}
|
||||
</div>
|
||||
<div>
|
||||
<Trans>Price range</Trans>
|
||||
<Trans>Status</Trans>
|
||||
</div>
|
||||
</DesktopHeader>
|
||||
<MobileHeader>
|
||||
|
||||
@@ -9,14 +9,16 @@ import styled from 'styled-components/macro'
|
||||
import { HideSmall, MEDIA_WIDTHS, SmallOnly } from 'theme'
|
||||
import { PositionDetails } from 'types/position'
|
||||
import { Price, Token, Percent } from '@uniswap/sdk-core'
|
||||
import { formatPrice } from 'utils/formatCurrencyAmount'
|
||||
import { formatTickPrice } from 'utils/formatTickPrice'
|
||||
import Loader from 'components/Loader'
|
||||
import { unwrappedToken } from 'utils/unwrappedToken'
|
||||
import RangeBadge from 'components/Badge/RangeBadge'
|
||||
import { RowFixed } from 'components/Row'
|
||||
import { RowBetween } from 'components/Row'
|
||||
import HoverInlineText from 'components/HoverInlineText'
|
||||
import { DAI, USDC, USDT, WBTC, WETH9_EXTENDED } from '../../constants/tokens'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import useIsTickAtLimit from 'hooks/useIsTickAtLimit'
|
||||
import { Bound } from 'state/mint/v3/actions'
|
||||
|
||||
const LinkRow = styled(Link)`
|
||||
align-items: center;
|
||||
@@ -24,6 +26,9 @@ const LinkRow = styled(Link)`
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
justify-content: space-between;
|
||||
color: ${({ theme }) => theme.text1};
|
||||
margin: 8px 0;
|
||||
@@ -32,25 +37,23 @@ const LinkRow = styled(Link)`
|
||||
font-weight: 500;
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
|
||||
&:first-of-type {
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
&:last-of-type {
|
||||
margin: 8px 0 0 0;
|
||||
}
|
||||
& > div:not(:first-child) {
|
||||
text-align: right;
|
||||
text-align: center;
|
||||
}
|
||||
:hover {
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
}
|
||||
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
flex-direction: row;
|
||||
/* flex-direction: row; */
|
||||
}
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
flex-direction: column;
|
||||
row-gap: 24px;
|
||||
row-gap: 12px;
|
||||
`};
|
||||
`
|
||||
|
||||
@@ -70,11 +73,14 @@ const RangeLineItem = styled(DataLineItem)`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-self: flex-end;
|
||||
|
||||
margin-top: 4px;
|
||||
width: 100%;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
flex-direction: column;
|
||||
row-gap: 4px;
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
border-radius: 12px;
|
||||
padding: 8px 0;
|
||||
`};
|
||||
`
|
||||
|
||||
@@ -97,6 +103,9 @@ const ExtentsText = styled.span`
|
||||
color: ${({ theme }) => theme.text3};
|
||||
font-size: 14px;
|
||||
margin-right: 4px;
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
display: none;
|
||||
`};
|
||||
`
|
||||
|
||||
const PrimaryPositionIdData = styled.div`
|
||||
@@ -117,7 +126,7 @@ const DataText = styled.div`
|
||||
`};
|
||||
`
|
||||
|
||||
export interface PositionListItemProps {
|
||||
interface PositionListItemProps {
|
||||
positionDetails: PositionDetails
|
||||
}
|
||||
|
||||
@@ -201,6 +210,8 @@ export default function PositionListItem({ positionDetails }: PositionListItemPr
|
||||
return undefined
|
||||
}, [liquidity, pool, tickLower, tickUpper])
|
||||
|
||||
const tickAtLimit = useIsTickAtLimit(feeAmount, tickLower, tickUpper)
|
||||
|
||||
// prices
|
||||
const { priceLower, priceUpper, quote, base } = getPriceOrderingFromPositionForUI(position)
|
||||
|
||||
@@ -216,7 +227,7 @@ export default function PositionListItem({ positionDetails }: PositionListItemPr
|
||||
|
||||
return (
|
||||
<LinkRow to={positionSummaryLink}>
|
||||
<RowFixed>
|
||||
<RowBetween>
|
||||
<PrimaryPositionIdData>
|
||||
<DoubleCurrencyLogo currency0={currencyBase} currency1={currencyQuote} size={18} margin />
|
||||
<DataText>
|
||||
@@ -230,7 +241,7 @@ export default function PositionListItem({ positionDetails }: PositionListItemPr
|
||||
</Badge>
|
||||
</PrimaryPositionIdData>
|
||||
<RangeBadge removed={removed} inRange={!outOfRange} />
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
|
||||
{priceLower && priceUpper ? (
|
||||
<RangeLineItem>
|
||||
@@ -239,23 +250,23 @@ export default function PositionListItem({ positionDetails }: PositionListItemPr
|
||||
<Trans>Min: </Trans>
|
||||
</ExtentsText>
|
||||
<Trans>
|
||||
{formatPrice(priceLower, 5)} <HoverInlineText text={currencyQuote?.symbol} /> per{' '}
|
||||
<HoverInlineText text={currencyBase?.symbol ?? ''} />
|
||||
{formatTickPrice(priceLower, tickAtLimit, Bound.LOWER)} <HoverInlineText text={currencyQuote?.symbol} />{' '}
|
||||
per <HoverInlineText text={currencyBase?.symbol ?? ''} />
|
||||
</Trans>
|
||||
</RangeText>{' '}
|
||||
<HideSmall>
|
||||
<DoubleArrow>⟷</DoubleArrow>{' '}
|
||||
</HideSmall>
|
||||
<SmallOnly>
|
||||
<DoubleArrow>↕</DoubleArrow>{' '}
|
||||
<DoubleArrow>⟷</DoubleArrow>{' '}
|
||||
</SmallOnly>
|
||||
<RangeText>
|
||||
<ExtentsText>
|
||||
<Trans>Max:</Trans>
|
||||
</ExtentsText>
|
||||
<Trans>
|
||||
{formatPrice(priceUpper, 5)} <HoverInlineText text={currencyQuote?.symbol} /> per{' '}
|
||||
<HoverInlineText maxCharacters={10} text={currencyBase?.symbol} />
|
||||
{formatTickPrice(priceUpper, tickAtLimit, Bound.UPPER)} <HoverInlineText text={currencyQuote?.symbol} />{' '}
|
||||
per <HoverInlineText maxCharacters={10} text={currencyBase?.symbol} />
|
||||
</Trans>
|
||||
</RangeText>
|
||||
</RangeLineItem>
|
||||
|
||||
@@ -12,19 +12,23 @@ import { Currency } from '@uniswap/sdk-core'
|
||||
import RateToggle from 'components/RateToggle'
|
||||
import DoubleCurrencyLogo from 'components/DoubleLogo'
|
||||
import RangeBadge from 'components/Badge/RangeBadge'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { ThemeContext } from 'styled-components/macro'
|
||||
import JSBI from 'jsbi'
|
||||
import { Bound } from 'state/mint/v3/actions'
|
||||
import { formatTickPrice } from 'utils/formatTickPrice'
|
||||
|
||||
export const PositionPreview = ({
|
||||
position,
|
||||
title,
|
||||
inRange,
|
||||
baseCurrencyDefault,
|
||||
ticksAtLimit,
|
||||
}: {
|
||||
position: Position
|
||||
title?: ReactNode
|
||||
inRange: boolean
|
||||
baseCurrencyDefault?: Currency | undefined
|
||||
ticksAtLimit: { [bound: string]: boolean | undefined }
|
||||
}) => {
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
@@ -121,7 +125,11 @@ export const PositionPreview = ({
|
||||
<TYPE.main fontSize="12px">
|
||||
<Trans>Min Price</Trans>
|
||||
</TYPE.main>
|
||||
<TYPE.mediumHeader textAlign="center">{`${priceLower.toSignificant(5)}`}</TYPE.mediumHeader>
|
||||
<TYPE.mediumHeader textAlign="center">{`${formatTickPrice(
|
||||
priceLower,
|
||||
ticksAtLimit,
|
||||
Bound.LOWER
|
||||
)}`}</TYPE.mediumHeader>
|
||||
<TYPE.main textAlign="center" fontSize="12px">
|
||||
<Trans>
|
||||
{quoteCurrency.symbol} per {baseCurrency.symbol}
|
||||
@@ -138,7 +146,11 @@ export const PositionPreview = ({
|
||||
<TYPE.main fontSize="12px">
|
||||
<Trans>Max Price</Trans>
|
||||
</TYPE.main>
|
||||
<TYPE.mediumHeader textAlign="center">{`${priceUpper.toSignificant(5)}`}</TYPE.mediumHeader>
|
||||
<TYPE.mediumHeader textAlign="center">{`${formatTickPrice(
|
||||
priceUpper,
|
||||
ticksAtLimit,
|
||||
Bound.UPPER
|
||||
)}`}</TYPE.mediumHeader>
|
||||
<TYPE.main textAlign="center" fontSize="12px">
|
||||
<Trans>
|
||||
{quoteCurrency.symbol} per {baseCurrency.symbol}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useContext } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { ThemeContext } from 'styled-components/macro'
|
||||
import { TYPE } from '../../theme'
|
||||
|
||||
const Wrapper = styled(AutoColumn)`
|
||||
|
||||
84
src/components/RangeSelector/PresetsButtons.tsx
Normal file
84
src/components/RangeSelector/PresetsButtons.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import React from 'react'
|
||||
import { ButtonOutlined } from 'components/Button'
|
||||
import { AutoRow } from 'components/Row'
|
||||
import { TYPE } from 'theme'
|
||||
import styled from 'styled-components/macro'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import ReactGA from 'react-ga'
|
||||
|
||||
const Button = styled(ButtonOutlined).attrs(() => ({
|
||||
padding: '4px',
|
||||
borderRadius: '8px',
|
||||
}))`
|
||||
color: ${({ theme }) => theme.text1};
|
||||
flex: 1;
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
`
|
||||
|
||||
const RANGES = {
|
||||
[FeeAmount.LOW]: [
|
||||
{ label: '0.05', ticks: 5 },
|
||||
{ label: '0.1', ticks: 10 },
|
||||
{ label: '0.2', ticks: 20 },
|
||||
],
|
||||
[FeeAmount.MEDIUM]: [
|
||||
{ label: '1', ticks: 100 },
|
||||
{ label: '10', ticks: 953 },
|
||||
{ label: '50', ticks: 4055 },
|
||||
],
|
||||
[FeeAmount.HIGH]: [
|
||||
{ label: '2', ticks: 198 },
|
||||
{ label: '10', ticks: 953 },
|
||||
{ label: '80', ticks: 5878 },
|
||||
],
|
||||
}
|
||||
|
||||
interface PresetsButtonProps {
|
||||
feeAmount: FeeAmount | undefined
|
||||
setRange: (numTicks: number) => void
|
||||
setFullRange: () => void
|
||||
}
|
||||
|
||||
const PresetButton = ({
|
||||
values: { label, ticks },
|
||||
setRange,
|
||||
}: {
|
||||
values: {
|
||||
label: string
|
||||
ticks: number
|
||||
}
|
||||
setRange: (numTicks: number) => void
|
||||
}) => (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setRange(ticks)
|
||||
ReactGA.event({
|
||||
category: 'Liquidity',
|
||||
action: 'Preset clicked',
|
||||
label: label,
|
||||
})
|
||||
}}
|
||||
>
|
||||
<TYPE.body fontSize={12}>
|
||||
<Trans>+/- {label}%</Trans>
|
||||
</TYPE.body>
|
||||
</Button>
|
||||
)
|
||||
|
||||
export default function PresetsButtons({ feeAmount, setRange, setFullRange }: PresetsButtonProps) {
|
||||
feeAmount = feeAmount ?? FeeAmount.LOW
|
||||
|
||||
return (
|
||||
<AutoRow gap="4px" width="auto">
|
||||
<PresetButton values={RANGES[feeAmount][0]} setRange={setRange} />
|
||||
<PresetButton values={RANGES[feeAmount][1]} setRange={setRange} />
|
||||
<PresetButton values={RANGES[feeAmount][2]} setRange={setRange} />
|
||||
<Button onClick={() => setFullRange()}>
|
||||
<TYPE.body fontSize={12}>
|
||||
<Trans>Full Range</Trans>
|
||||
</TYPE.body>
|
||||
</Button>
|
||||
</AutoRow>
|
||||
)
|
||||
}
|
||||
@@ -2,6 +2,8 @@ import { Trans } from '@lingui/macro'
|
||||
import { Currency, Price, Token } from '@uniswap/sdk-core'
|
||||
import StepCounter from 'components/InputStepCounter/InputStepCounter'
|
||||
import { RowBetween } from 'components/Row'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { Bound } from 'state/mint/v3/actions'
|
||||
|
||||
// currencyA is the base token
|
||||
export default function RangeSelector({
|
||||
@@ -16,6 +18,7 @@ export default function RangeSelector({
|
||||
currencyA,
|
||||
currencyB,
|
||||
feeAmount,
|
||||
ticksAtLimit,
|
||||
}: {
|
||||
priceLower?: Price<Token, Token>
|
||||
priceUpper?: Price<Token, Token>
|
||||
@@ -28,6 +31,7 @@ export default function RangeSelector({
|
||||
currencyA?: Currency | null
|
||||
currencyB?: Currency | null
|
||||
feeAmount?: number
|
||||
ticksAtLimit: { [bound in Bound]?: boolean | undefined }
|
||||
}) {
|
||||
const tokenA = (currencyA ?? undefined)?.wrapped
|
||||
const tokenB = (currencyB ?? undefined)?.wrapped
|
||||
@@ -37,31 +41,37 @@ export default function RangeSelector({
|
||||
const rightPrice = isSorted ? priceUpper : priceLower?.invert()
|
||||
|
||||
return (
|
||||
<RowBetween>
|
||||
<StepCounter
|
||||
value={leftPrice?.toSignificant(5) ?? ''}
|
||||
onUserInput={onLeftRangeInput}
|
||||
width="48%"
|
||||
decrement={isSorted ? getDecrementLower : getIncrementUpper}
|
||||
increment={isSorted ? getIncrementLower : getDecrementUpper}
|
||||
feeAmount={feeAmount}
|
||||
label={leftPrice ? `${currencyB?.symbol}` : '-'}
|
||||
title={<Trans>Min Price</Trans>}
|
||||
tokenA={currencyA?.symbol}
|
||||
tokenB={currencyB?.symbol}
|
||||
/>
|
||||
<StepCounter
|
||||
value={rightPrice?.toSignificant(5) ?? ''}
|
||||
onUserInput={onRightRangeInput}
|
||||
width="48%"
|
||||
decrement={isSorted ? getDecrementUpper : getIncrementLower}
|
||||
increment={isSorted ? getIncrementUpper : getDecrementLower}
|
||||
feeAmount={feeAmount}
|
||||
label={rightPrice ? `${currencyB?.symbol}` : '-'}
|
||||
tokenA={currencyA?.symbol}
|
||||
tokenB={currencyB?.symbol}
|
||||
title={<Trans>Max Price</Trans>}
|
||||
/>
|
||||
</RowBetween>
|
||||
<AutoColumn gap="md">
|
||||
<RowBetween>
|
||||
<StepCounter
|
||||
value={ticksAtLimit[Bound.LOWER] ? '0' : leftPrice?.toSignificant(5) ?? ''}
|
||||
onUserInput={onLeftRangeInput}
|
||||
width="48%"
|
||||
decrement={isSorted ? getDecrementLower : getIncrementUpper}
|
||||
increment={isSorted ? getIncrementLower : getDecrementUpper}
|
||||
decrementDisabled={ticksAtLimit[Bound.LOWER]}
|
||||
incrementDisabled={ticksAtLimit[Bound.LOWER]}
|
||||
feeAmount={feeAmount}
|
||||
label={leftPrice ? `${currencyB?.symbol}` : '-'}
|
||||
title={<Trans>Min Price</Trans>}
|
||||
tokenA={currencyA?.symbol}
|
||||
tokenB={currencyB?.symbol}
|
||||
/>
|
||||
<StepCounter
|
||||
value={ticksAtLimit[Bound.UPPER] ? '∞' : rightPrice?.toSignificant(5) ?? ''}
|
||||
onUserInput={onRightRangeInput}
|
||||
width="48%"
|
||||
decrement={isSorted ? getDecrementUpper : getIncrementLower}
|
||||
increment={isSorted ? getIncrementUpper : getDecrementLower}
|
||||
incrementDisabled={ticksAtLimit[Bound.UPPER]}
|
||||
decrementDisabled={ticksAtLimit[Bound.UPPER]}
|
||||
feeAmount={feeAmount}
|
||||
label={rightPrice ? `${currencyB?.symbol}` : '-'}
|
||||
tokenA={currencyA?.symbol}
|
||||
tokenB={currencyB?.symbol}
|
||||
title={<Trans>Max Price</Trans>}
|
||||
/>
|
||||
</RowBetween>
|
||||
</AutoColumn>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -22,10 +22,10 @@ export default function RateToggle({
|
||||
<div style={{ width: 'fit-content', display: 'flex', alignItems: 'center' }} onClick={handleRateToggle}>
|
||||
<ToggleWrapper width="fit-content">
|
||||
<ToggleElement isActive={isSorted} fontSize="12px">
|
||||
<Trans>{isSorted ? currencyA.symbol : currencyB.symbol} price</Trans>
|
||||
<Trans>{isSorted ? currencyA.symbol : currencyB.symbol}</Trans>
|
||||
</ToggleElement>
|
||||
<ToggleElement isActive={!isSorted} fontSize="12px">
|
||||
<Trans>{isSorted ? currencyB.symbol : currencyA.symbol} price</Trans>
|
||||
<Trans>{isSorted ? currencyB.symbol : currencyA.symbol}</Trans>
|
||||
</ToggleElement>
|
||||
</ToggleWrapper>
|
||||
</div>
|
||||
|
||||
@@ -162,7 +162,7 @@ function BreakLineComponent({ style }: { style: CSSProperties }) {
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<FixedContentRow style={style}>
|
||||
<LightGreyCard padding="8px 12px" borderRadius="8px">
|
||||
<LightGreyCard padding="8px 12px" $borderRadius="8px">
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<TokenListLogoWrapper src={TokenListLogo} />
|
||||
|
||||
@@ -149,7 +149,7 @@ export function ImportList({ listURL, list, setModalView, onDismiss }: ImportPro
|
||||
<ButtonPrimary
|
||||
disabled={!confirmed}
|
||||
altDisabledStyle={true}
|
||||
borderRadius="20px"
|
||||
$borderRadius="20px"
|
||||
padding="10px 1rem"
|
||||
onClick={handleAddList}
|
||||
>
|
||||
|
||||
@@ -108,7 +108,7 @@ export function ImportToken({ tokens, list, onBack, onDismiss, handleCurrencySel
|
||||
</TYPE.small>
|
||||
</RowFixed>
|
||||
) : (
|
||||
<WarningWrapper borderRadius="4px" padding="4px" highWarning={true}>
|
||||
<WarningWrapper $borderRadius="4px" padding="4px" highWarning={true}>
|
||||
<RowFixed>
|
||||
<AlertCircle stroke={theme.red1} size="10px" />
|
||||
<TYPE.body color={theme.red1} ml="4px" fontSize="10px" fontWeight={500}>
|
||||
@@ -124,7 +124,7 @@ export function ImportToken({ tokens, list, onBack, onDismiss, handleCurrencySel
|
||||
|
||||
<ButtonPrimary
|
||||
altDisabledStyle={true}
|
||||
borderRadius="20px"
|
||||
$borderRadius="20px"
|
||||
padding="10px 1rem"
|
||||
onClick={() => {
|
||||
tokens.map((token) => addToken(token))
|
||||
|
||||
@@ -1,36 +1,34 @@
|
||||
import { memo, useCallback, useMemo, useRef, useState, useEffect } from 'react'
|
||||
import { Settings, CheckCircle } from 'react-feather'
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { TokenList } from '@uniswap/token-lists'
|
||||
import Card from 'components/Card'
|
||||
import { UNSUPPORTED_LIST_URLS } from 'constants/lists'
|
||||
import { useListColor } from 'hooks/useColor'
|
||||
import { useActiveWeb3React } from 'hooks/web3'
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { CheckCircle, Settings } from 'react-feather'
|
||||
import ReactGA from 'react-ga'
|
||||
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||
import { usePopper } from 'react-popper'
|
||||
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { useFetchListCallback } from '../../hooks/useFetchListCallback'
|
||||
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
|
||||
import { TokenList } from '@uniswap/token-lists'
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
|
||||
import useTheme from '../../hooks/useTheme'
|
||||
import useToggle from '../../hooks/useToggle'
|
||||
import { acceptListUpdate, removeList, disableList, enableList } from '../../state/lists/actions'
|
||||
import { useIsListActive, useAllLists, useActiveListUrls } from '../../state/lists/hooks'
|
||||
import { ExternalLink, LinkStyledButton, TYPE, IconWrapper } from '../../theme'
|
||||
import { acceptListUpdate, disableList, enableList, removeList } from '../../state/lists/actions'
|
||||
import { useActiveListUrls, useAllLists, useIsListActive } from '../../state/lists/hooks'
|
||||
import { ExternalLink, IconWrapper, LinkStyledButton, TYPE } from '../../theme'
|
||||
import listVersionLabel from '../../utils/listVersionLabel'
|
||||
import { parseENSAddress } from '../../utils/parseENSAddress'
|
||||
import uriToHttp from '../../utils/uriToHttp'
|
||||
import { ButtonEmpty, ButtonPrimary } from '../Button'
|
||||
|
||||
import Column, { AutoColumn } from '../Column'
|
||||
import ListLogo from '../ListLogo'
|
||||
import Row, { RowFixed, RowBetween } from '../Row'
|
||||
import { PaddedColumn, SearchInput, Separator, SeparatorDark } from './styleds'
|
||||
import { useListColor } from 'hooks/useColor'
|
||||
import useTheme from '../../hooks/useTheme'
|
||||
import Row, { RowBetween, RowFixed } from '../Row'
|
||||
import ListToggle from '../Toggle/ListToggle'
|
||||
import Card from 'components/Card'
|
||||
import { CurrencyModalView } from './CurrencySearchModal'
|
||||
import { UNSUPPORTED_LIST_URLS } from 'constants/lists'
|
||||
import { PaddedColumn, SearchInput, Separator, SeparatorDark } from './styleds'
|
||||
|
||||
const Wrapper = styled(Column)`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
`
|
||||
|
||||
@@ -80,8 +78,9 @@ const StyledListUrlText = styled(TYPE.main)<{ active: boolean }>`
|
||||
color: ${({ theme, active }) => (active ? theme.white : theme.text2)};
|
||||
`
|
||||
|
||||
const RowWrapper = styled(Row)<{ bgColor: string; active: boolean }>`
|
||||
const RowWrapper = styled(Row)<{ bgColor: string; active: boolean; hasActiveTokens: boolean }>`
|
||||
background-color: ${({ bgColor, active, theme }) => (active ? bgColor ?? 'transparent' : theme.bg2)};
|
||||
opacity: ${({ hasActiveTokens }) => (hasActiveTokens ? 1 : 0.4)};
|
||||
transition: 200ms;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
@@ -93,10 +92,18 @@ function listUrlRowHTMLId(listUrl: string) {
|
||||
}
|
||||
|
||||
const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const listsByUrl = useAppSelector((state) => state.lists.byUrl)
|
||||
const dispatch = useAppDispatch()
|
||||
const { current: list, pendingUpdate: pending } = listsByUrl[listUrl]
|
||||
|
||||
const activeTokensOnThisChain = useMemo(() => {
|
||||
if (!list || !chainId) {
|
||||
return 0
|
||||
}
|
||||
return list.tokens.reduce((acc, cur) => (cur.chainId === chainId ? acc + 1 : acc), 0)
|
||||
}, [chainId, list])
|
||||
|
||||
const theme = useTheme()
|
||||
const listColor = useListColor(list?.logoURI)
|
||||
const isActive = useIsListActive(listUrl)
|
||||
@@ -130,7 +137,7 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
|
||||
action: 'Start Remove List',
|
||||
label: listUrl,
|
||||
})
|
||||
if (window.prompt(`Please confirm you would like to remove this list by typing REMOVE`) === `REMOVE`) {
|
||||
if (window.prompt(t`Please confirm you would like to remove this list by typing REMOVE`) === `REMOVE`) {
|
||||
ReactGA.event({
|
||||
category: 'Lists',
|
||||
action: 'Confirm Remove List',
|
||||
@@ -161,7 +168,13 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
|
||||
if (!list) return null
|
||||
|
||||
return (
|
||||
<RowWrapper active={isActive} bgColor={listColor} key={listUrl} id={listUrlRowHTMLId(listUrl)}>
|
||||
<RowWrapper
|
||||
active={isActive}
|
||||
hasActiveTokens={activeTokensOnThisChain > 0}
|
||||
bgColor={listColor}
|
||||
key={listUrl}
|
||||
id={listUrlRowHTMLId(listUrl)}
|
||||
>
|
||||
{list.logoURI ? (
|
||||
<ListLogo size="40px" style={{ marginRight: '1rem' }} logoURI={list.logoURI} alt={`${list.name} list logo`} />
|
||||
) : (
|
||||
@@ -173,7 +186,7 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
|
||||
</Row>
|
||||
<RowFixed mt="4px">
|
||||
<StyledListUrlText active={isActive} mr="6px">
|
||||
<Trans>{list.tokens.length} tokens</Trans>
|
||||
<Trans>{activeTokensOnThisChain} tokens</Trans>
|
||||
</StyledListUrlText>
|
||||
<StyledMenu ref={node as any}>
|
||||
<ButtonEmpty onClick={toggle} ref={setReferenceElement} padding="0">
|
||||
@@ -226,20 +239,29 @@ export function ManageLists({
|
||||
setImportList: (list: TokenList) => void
|
||||
setListUrl: (url: string) => void
|
||||
}) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const theme = useTheme()
|
||||
|
||||
const [listUrlInput, setListUrlInput] = useState<string>('')
|
||||
|
||||
const lists = useAllLists()
|
||||
|
||||
const tokenCountByListName = useMemo<Record<string, number>>(
|
||||
() =>
|
||||
Object.values(lists).reduce((acc, { current: list }) => {
|
||||
if (!list) {
|
||||
return acc
|
||||
}
|
||||
return {
|
||||
...acc,
|
||||
[list.name]: list.tokens.reduce((count: number, token) => (token.chainId === chainId ? count + 1 : count), 0),
|
||||
}
|
||||
}, {}),
|
||||
[chainId, lists]
|
||||
)
|
||||
|
||||
// sort by active but only if not visible
|
||||
const activeListUrls = useActiveListUrls()
|
||||
const [activeCopy, setActiveCopy] = useState<string[] | undefined>()
|
||||
useEffect(() => {
|
||||
if (!activeCopy && activeListUrls) {
|
||||
setActiveCopy(activeListUrls)
|
||||
}
|
||||
}, [activeCopy, activeListUrls])
|
||||
|
||||
const handleInput = useCallback((e) => {
|
||||
setListUrlInput(e.target.value)
|
||||
@@ -258,30 +280,36 @@ export function ManageLists({
|
||||
// only show loaded lists, hide unsupported lists
|
||||
return Boolean(lists[listUrl].current) && !Boolean(UNSUPPORTED_LIST_URLS.includes(listUrl))
|
||||
})
|
||||
.sort((u1, u2) => {
|
||||
const { current: l1 } = lists[u1]
|
||||
const { current: l2 } = lists[u2]
|
||||
.sort((listUrlA, listUrlB) => {
|
||||
const { current: listA } = lists[listUrlA]
|
||||
const { current: listB } = lists[listUrlB]
|
||||
|
||||
// first filter on active lists
|
||||
if (activeCopy?.includes(u1) && !activeCopy?.includes(u2)) {
|
||||
if (activeListUrls?.includes(listUrlA) && !activeListUrls?.includes(listUrlB)) {
|
||||
return -1
|
||||
}
|
||||
if (!activeCopy?.includes(u1) && activeCopy?.includes(u2)) {
|
||||
if (!activeListUrls?.includes(listUrlA) && activeListUrls?.includes(listUrlB)) {
|
||||
return 1
|
||||
}
|
||||
|
||||
if (l1 && l2) {
|
||||
return l1.name.toLowerCase() < l2.name.toLowerCase()
|
||||
if (listA && listB) {
|
||||
if (tokenCountByListName[listA.name] > tokenCountByListName[listB.name]) {
|
||||
return -1
|
||||
}
|
||||
if (tokenCountByListName[listA.name] < tokenCountByListName[listB.name]) {
|
||||
return 1
|
||||
}
|
||||
return listA.name.toLowerCase() < listB.name.toLowerCase()
|
||||
? -1
|
||||
: l1.name.toLowerCase() === l2.name.toLowerCase()
|
||||
: listA.name.toLowerCase() === listB.name.toLowerCase()
|
||||
? 0
|
||||
: 1
|
||||
}
|
||||
if (l1) return -1
|
||||
if (l2) return 1
|
||||
if (listA) return -1
|
||||
if (listB) return 1
|
||||
return 0
|
||||
})
|
||||
}, [lists, activeCopy])
|
||||
}, [lists, activeListUrls, tokenCountByListName])
|
||||
|
||||
// temporary fetched list for import flow
|
||||
const [tempList, setTempList] = useState<TokenList>()
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { Text } from 'rebass'
|
||||
import styled from 'styled-components/macro'
|
||||
import { RowFixed } from '../Row'
|
||||
|
||||
export const FilterWrapper = styled(RowFixed)`
|
||||
padding: 8px;
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
color: ${({ theme }) => theme.text1};
|
||||
border-radius: 8px;
|
||||
user-select: none;
|
||||
& > * {
|
||||
user-select: none;
|
||||
}
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
|
||||
export default function SortButton({
|
||||
toggleSortOrder,
|
||||
ascending,
|
||||
}: {
|
||||
toggleSortOrder: () => void
|
||||
ascending: boolean
|
||||
}) {
|
||||
return (
|
||||
<FilterWrapper onClick={toggleSortOrder}>
|
||||
<Text fontSize={14} fontWeight={500}>
|
||||
{ascending ? '↑' : '↓'}
|
||||
</Text>
|
||||
</FilterWrapper>
|
||||
)
|
||||
}
|
||||
@@ -1,43 +1,6 @@
|
||||
import styled from 'styled-components/macro'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
|
||||
export const ModalInfo = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
align-items: center;
|
||||
padding: 1rem 1rem;
|
||||
margin: 0.25rem 0.5rem;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
user-select: none;
|
||||
`
|
||||
export const StyledMenu = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
border: none;
|
||||
`
|
||||
|
||||
export const PopoverContainer = styled.div<{ show: boolean }>`
|
||||
z-index: 100;
|
||||
visibility: ${(props) => (props.show ? 'visible' : 'hidden')};
|
||||
opacity: ${(props) => (props.show ? 1 : 0)};
|
||||
transition: visibility 150ms linear, opacity 150ms linear;
|
||||
background: ${({ theme }) => theme.bg2};
|
||||
border: 1px solid ${({ theme }) => theme.bg3};
|
||||
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);
|
||||
color: ${({ theme }) => theme.text2};
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr;
|
||||
grid-gap: 8px;
|
||||
font-size: 1rem;
|
||||
text-align: left;
|
||||
top: 80px;
|
||||
`
|
||||
import { RowBetween } from '../Row'
|
||||
|
||||
export const TextDot = styled.div`
|
||||
height: 3px;
|
||||
@@ -46,10 +9,6 @@ export const TextDot = styled.div`
|
||||
border-radius: 50%;
|
||||
`
|
||||
|
||||
export const FadedSpan = styled(RowFixed)`
|
||||
color: ${({ theme }) => theme.primary1};
|
||||
font-size: 14px;
|
||||
`
|
||||
export const Checkbox = styled.input`
|
||||
border: 1px solid ${({ theme }) => theme.red3};
|
||||
height: 20px;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useContext, useRef, useState } from 'react'
|
||||
import { Settings, X } from 'react-feather'
|
||||
import ReactGA from 'react-ga'
|
||||
import { Text } from 'rebass'
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
|
||||
import { ApplicationModal } from '../../state/application/actions'
|
||||
import { useModalOpen, useToggleSettingsMenu } from '../../state/application/hooks'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { memo, useCallback, useRef } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const Input = styled.input<{ error?: boolean; fontSize?: string }>`
|
||||
font-size: ${({ fontSize }) => fontSize || '1.25rem'};
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import { readableColor } from 'polished'
|
||||
import styled from 'styled-components/macro'
|
||||
import { colors } from 'theme'
|
||||
|
||||
const Swatch = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100px;
|
||||
justify-content: center;
|
||||
min-width: 200px;
|
||||
`
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
`
|
||||
|
||||
interface ThemePaletteProps {
|
||||
isDarkMode: boolean
|
||||
}
|
||||
|
||||
export default function ThemePalette({ isDarkMode }: ThemePaletteProps) {
|
||||
const data = colors(isDarkMode)
|
||||
return (
|
||||
<Wrapper>
|
||||
{Object.entries(data).map(([key, value]) => (
|
||||
<Swatch key={key} style={{ color: readableColor(value), backgroundColor: value }}>
|
||||
<div>{key}</div>
|
||||
<div>{value}</div>
|
||||
</Swatch>
|
||||
))}
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
@@ -30,7 +30,7 @@ const StatusText = styled(TYPE.main)<{ isActive?: boolean }>`
|
||||
color: ${({ theme, isActive }) => (isActive ? theme.text1 : theme.text3)};
|
||||
`
|
||||
|
||||
export interface ToggleProps {
|
||||
interface ToggleProps {
|
||||
id?: string
|
||||
isActive: boolean
|
||||
bgColor: string
|
||||
|
||||
@@ -8,7 +8,7 @@ const ToggleElement = styled.span<{ isActive?: boolean; isOnSwitch?: boolean }>`
|
||||
border-radius: 9px;
|
||||
background: ${({ theme, isActive, isOnSwitch }) => (isActive ? (isOnSwitch ? theme.primary1 : theme.bg4) : 'none')};
|
||||
color: ${({ theme, isActive }) => (isActive ? theme.white : theme.text2)};
|
||||
font-size: 1rem;
|
||||
font-size: 14px;
|
||||
font-weight: ${({ isOnSwitch }) => (isOnSwitch ? '500' : '400')};
|
||||
:hover {
|
||||
user-select: ${({ isOnSwitch }) => (isOnSwitch ? 'none' : 'initial')};
|
||||
@@ -20,9 +20,8 @@ const ToggleElement = styled.span<{ isActive?: boolean; isOnSwitch?: boolean }>`
|
||||
|
||||
const StyledToggle = styled.button<{ isActive?: boolean; activeElement?: boolean }>`
|
||||
border-radius: 12px;
|
||||
border: 2px solid;
|
||||
border-color: ${({ theme, isActive }) => (isActive ? theme.primary1 : theme.bg3)};
|
||||
background: ${({ theme }) => theme.bg1};
|
||||
border: none;
|
||||
background: ${({ theme }) => theme.bg0};
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
cursor: pointer;
|
||||
@@ -30,7 +29,7 @@ const StyledToggle = styled.button<{ isActive?: boolean; activeElement?: boolean
|
||||
padding: 2px;
|
||||
`
|
||||
|
||||
export interface ToggleProps {
|
||||
interface ToggleProps {
|
||||
id?: string
|
||||
isActive: boolean
|
||||
toggle: () => void
|
||||
|
||||
@@ -21,7 +21,7 @@ export default function Tooltip({ text, ...rest }: TooltipProps) {
|
||||
return <Popover content={<TooltipContainer>{text}</TooltipContainer>} {...rest} />
|
||||
}
|
||||
|
||||
export function TooltipContent({ content, ...rest }: TooltipContentProps) {
|
||||
function TooltipContent({ content, ...rest }: TooltipContentProps) {
|
||||
return <Popover content={<TooltipContainer>{content}</TooltipContainer>} {...rest} />
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { ReactNode, useContext } from 'react'
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
import { ReactNode, useContext, useEffect } from 'react'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
import { getExplorerLink, ExplorerDataType } from '../../utils/getExplorerLink'
|
||||
import Modal from '../Modal'
|
||||
import { ExternalLink } from '../../theme'
|
||||
@@ -15,6 +15,7 @@ import MetaMaskLogo from '../../assets/images/metamask.png'
|
||||
import { useActiveWeb3React } from '../../hooks/web3'
|
||||
import useAddTokenToMetamask from 'hooks/useAddTokenToMetamask'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { L2_CHAIN_IDS } from 'constants/chains'
|
||||
|
||||
const Wrapper = styled.div`
|
||||
width: 100%;
|
||||
@@ -39,7 +40,7 @@ const StyledLogo = styled.img`
|
||||
margin-left: 6px;
|
||||
`
|
||||
|
||||
export function ConfirmationPendingContent({
|
||||
function ConfirmationPendingContent({
|
||||
onDismiss,
|
||||
pendingText,
|
||||
inline,
|
||||
@@ -78,7 +79,7 @@ export function ConfirmationPendingContent({
|
||||
)
|
||||
}
|
||||
|
||||
export function TransactionSubmittedContent({
|
||||
function TransactionSubmittedContent({
|
||||
onDismiss,
|
||||
chainId,
|
||||
hash,
|
||||
@@ -227,12 +228,21 @@ export default function TransactionConfirmationModal({
|
||||
}: ConfirmationModalProps) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
|
||||
// if on L2 and txn is submitted, close automatically (if open)
|
||||
useEffect(() => {
|
||||
if (isOpen && chainId && L2_CHAIN_IDS.includes(chainId) && hash) {
|
||||
onDismiss()
|
||||
}
|
||||
}, [chainId, hash, isOpen, onDismiss])
|
||||
|
||||
if (!chainId) return null
|
||||
|
||||
// confirmation screen
|
||||
// if on L2 and submitted dont render content, as should auto dismiss
|
||||
// need this to skip submitted view during state update ^^
|
||||
return (
|
||||
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={90}>
|
||||
{attemptingTxn ? (
|
||||
{L2_CHAIN_IDS.includes(chainId) && hash ? null : attemptingTxn ? (
|
||||
<ConfirmationPendingContent onDismiss={onDismiss} pendingText={pendingText} />
|
||||
) : hash ? (
|
||||
<TransactionSubmittedContent
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { useState, useContext } from 'react'
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
|
||||
import QuestionHelper from '../QuestionHelper'
|
||||
import { TYPE } from '../../theme'
|
||||
@@ -87,7 +87,7 @@ const SlippageEmojiContainer = styled.span`
|
||||
`}
|
||||
`
|
||||
|
||||
export interface TransactionSettingsProps {
|
||||
interface TransactionSettingsProps {
|
||||
placeholderSlippage: Percent // varies according to the context in which the settings dialog is placed
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { AbstractConnector } from '@web3-react/abstract-connector'
|
||||
import { UnsupportedChainIdError, useWeb3React } from '@web3-react/core'
|
||||
import { darken, lighten } from 'polished'
|
||||
import { darken } from 'polished'
|
||||
import { useMemo } from 'react'
|
||||
import { Activity } from 'react-feather'
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import styled, { css } from 'styled-components'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
|
||||
import FortmaticIcon from '../../assets/images/fortmaticIcon.png'
|
||||
import PortisIcon from '../../assets/images/portisIcon.png'
|
||||
@@ -61,6 +61,7 @@ const Web3StatusError = styled(Web3StatusGeneric)`
|
||||
const Web3StatusConnect = styled(Web3StatusGeneric)<{ faded?: boolean }>`
|
||||
background-color: ${({ theme }) => theme.primary4};
|
||||
border: none;
|
||||
|
||||
color: ${({ theme }) => theme.primaryText1};
|
||||
font-weight: 500;
|
||||
|
||||
@@ -86,13 +87,13 @@ const Web3StatusConnect = styled(Web3StatusGeneric)<{ faded?: boolean }>`
|
||||
`
|
||||
|
||||
const Web3StatusConnected = styled(Web3StatusGeneric)<{ pending?: boolean }>`
|
||||
background-color: ${({ pending, theme }) => (pending ? theme.primary1 : theme.bg1)};
|
||||
border: 1px solid ${({ pending, theme }) => (pending ? theme.primary1 : theme.bg2)};
|
||||
background-color: ${({ pending, theme }) => (pending ? theme.primary1 : theme.bg0)};
|
||||
border: 1px solid ${({ pending, theme }) => (pending ? theme.primary1 : theme.bg1)};
|
||||
color: ${({ pending, theme }) => (pending ? theme.white : theme.text1)};
|
||||
font-weight: 500;
|
||||
:hover,
|
||||
:focus {
|
||||
background-color: ${({ pending, theme }) => (pending ? darken(0.05, theme.primary1) : lighten(0.05, theme.bg1))};
|
||||
border: 1px solid ${({ theme }) => darken(0.05, theme.bg3)};
|
||||
|
||||
:focus {
|
||||
border: 1px solid ${({ pending, theme }) => (pending ? darken(0.1, theme.primary1) : darken(0.1, theme.bg2))};
|
||||
|
||||
@@ -132,7 +132,7 @@ export default function AddressClaimModal({ isOpen, onDismiss }: { isOpen: boole
|
||||
disabled={!isAddress(parsedAddress ?? '') || !hasAvailableClaim}
|
||||
padding="16px 16px"
|
||||
width="100%"
|
||||
borderRadius="12px"
|
||||
$borderRadius="12px"
|
||||
mt="1rem"
|
||||
onClick={onClaim}
|
||||
>
|
||||
|
||||
@@ -161,7 +161,7 @@ export default function ClaimModal() {
|
||||
disabled={!isAddress(account ?? '')}
|
||||
padding="16px 16px"
|
||||
width="100%"
|
||||
borderRadius="12px"
|
||||
$borderRadius="12px"
|
||||
mt="1rem"
|
||||
onClick={onClaim}
|
||||
>
|
||||
|
||||
@@ -119,7 +119,7 @@ export default function PoolCard({ stakingInfo }: { stakingInfo: StakingInfo })
|
||||
</TYPE.white>
|
||||
|
||||
<StyledInternalLink to={`/uni/${currencyId(currency0)}/${currencyId(currency1)}`} style={{ width: '100%' }}>
|
||||
<ButtonPrimary padding="8px" borderRadius="8px">
|
||||
<ButtonPrimary padding="8px" $borderRadius="8px">
|
||||
{isStaking ? <Trans>Manage</Trans> : <Trans>Deposit</Trans>}
|
||||
</ButtonPrimary>
|
||||
</StyledInternalLink>
|
||||
@@ -134,7 +134,7 @@ export default function PoolCard({ stakingInfo }: { stakingInfo: StakingInfo })
|
||||
{valueOfTotalStakedAmountInUSDC ? (
|
||||
<Trans>${valueOfTotalStakedAmountInUSDC.toFixed(0, { groupSeparator: ',' })}</Trans>
|
||||
) : (
|
||||
<Trans>${valueOfTotalStakedAmountInWETH?.toSignificant(4, { groupSeparator: ',' }) ?? '-'} ETH</Trans>
|
||||
<Trans>{valueOfTotalStakedAmountInWETH?.toSignificant(4, { groupSeparator: ',' }) ?? '-'} ETH</Trans>
|
||||
)}
|
||||
</TYPE.white>
|
||||
</RowBetween>
|
||||
|
||||
@@ -5,17 +5,6 @@ import uImage from '../../assets/images/big_unicorn.png'
|
||||
import xlUnicorn from '../../assets/images/xl_uni.png'
|
||||
import noise from '../../assets/images/noise.png'
|
||||
|
||||
export const TextBox = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px 12px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
border-radius: 20px;
|
||||
width: fit-content;
|
||||
justify-self: flex-end;
|
||||
`
|
||||
|
||||
export const DataCard = styled(AutoColumn)<{ disabled?: boolean }>`
|
||||
background: radial-gradient(76.02% 75.41% at 1.84% 0%, #ff007a 0%, #2172e5 100%);
|
||||
border-radius: 12px;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Percent, Currency, TradeType } from '@uniswap/sdk-core'
|
||||
import { Trade as V2Trade } from '@uniswap/v2-sdk'
|
||||
import { Trade as V3Trade } from '@uniswap/v3-sdk'
|
||||
import { useContext, useMemo } from 'react'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { ThemeContext } from 'styled-components/macro'
|
||||
import { TYPE } from '../../theme'
|
||||
import { computeRealizedLPFeePercent } from '../../utils/prices'
|
||||
import { AutoColumn } from '../Column'
|
||||
@@ -11,7 +11,7 @@ import { RowBetween, RowFixed } from '../Row'
|
||||
import FormattedPriceImpact from './FormattedPriceImpact'
|
||||
import SwapRoute from './SwapRoute'
|
||||
|
||||
export interface AdvancedSwapDetailsProps {
|
||||
interface AdvancedSwapDetailsProps {
|
||||
trade?: V2Trade<Currency, Currency, TradeType> | V3Trade<Currency, Currency, TradeType>
|
||||
allowedSlippage: Percent
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import styled from 'styled-components/macro'
|
||||
import { useLastTruthy } from '../../hooks/useLast'
|
||||
import { AdvancedSwapDetails, AdvancedSwapDetailsProps } from './AdvancedSwapDetails'
|
||||
|
||||
const AdvancedDetailsFooter = styled.div<{ show: boolean }>`
|
||||
width: 100%;
|
||||
border-bottom-left-radius: 20px;
|
||||
border-bottom-right-radius: 20px;
|
||||
color: ${({ theme }) => theme.text2};
|
||||
`
|
||||
|
||||
export default function AdvancedSwapDetailsDropdown({ trade, ...rest }: AdvancedSwapDetailsProps) {
|
||||
const lastTrade = useLastTruthy(trade)
|
||||
|
||||
return (
|
||||
<AdvancedDetailsFooter show={Boolean(trade)}>
|
||||
<AdvancedSwapDetails {...rest} trade={trade ?? lastTrade ?? undefined} />
|
||||
</AdvancedDetailsFooter>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
import { warningSeverity } from '../../utils/prices'
|
||||
import { ErrorText, ErrorPill } from './styleds'
|
||||
import { ErrorText } from './styleds'
|
||||
|
||||
/**
|
||||
* Formatted version of price impact text with warning colors
|
||||
@@ -12,11 +12,3 @@ export default function FormattedPriceImpact({ priceImpact }: { priceImpact?: Pe
|
||||
</ErrorText>
|
||||
)
|
||||
}
|
||||
|
||||
export function SmallFormattedPriceImpact({ priceImpact }: { priceImpact?: Percent }) {
|
||||
return (
|
||||
<ErrorPill fontWeight={500} fontSize={12} severity={warningSeverity(priceImpact)}>
|
||||
{priceImpact ? `(${priceImpact.multiply(-1).toFixed(2)}%)` : '-'}
|
||||
</ErrorPill>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Trade as V3Trade } from '@uniswap/v3-sdk'
|
||||
import { useContext, useState } from 'react'
|
||||
import { ArrowDown, AlertTriangle } from 'react-feather'
|
||||
import { Text } from 'rebass'
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
import { useUSDCValue } from '../../hooks/useUSDCPrice'
|
||||
import { TYPE } from '../../theme'
|
||||
import { ButtonPrimary } from '../Button'
|
||||
@@ -22,7 +22,7 @@ import { LightCard } from '../Card'
|
||||
|
||||
import TradePrice from '../swap/TradePrice'
|
||||
|
||||
export const ArrowWrapper = styled.div`
|
||||
const ArrowWrapper = styled.div`
|
||||
padding: 4px;
|
||||
border-radius: 12px;
|
||||
height: 32px;
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import arbitrumMaskUrl from 'assets/svg/arbitrum_mask.svg'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { useActiveWeb3React } from 'hooks/web3'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { ArrowDownCircle, X } from 'react-feather'
|
||||
import { useArbitrumAlphaAlert } from 'state/user/hooks'
|
||||
import { useETHBalances } from 'state/wallet/hooks'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const CloseIcon = styled(X)`
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 1em;
|
||||
right: 1em;
|
||||
`
|
||||
const Wrapper = styled.div`
|
||||
border-radius: 20px;
|
||||
background: radial-gradient(285.11% 8200.45% at 29.05% 48.94%, rgba(40, 160, 240, 0.1) 0%, rgba(219, 255, 0, 0) 100%),
|
||||
radial-gradient(76.02% 75.41% at 1.84% 0%, rgba(150, 190, 220, 0.3) 0%, rgba(33, 114, 229, 0.3) 100%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 480px;
|
||||
min-height: 212px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
background-image: url(${arbitrumMaskUrl});
|
||||
background-repeat: no-repeat;
|
||||
transform: rotate(15deg), scale(1);
|
||||
}
|
||||
`
|
||||
const ArbitrumTextStyles = styled.span`
|
||||
font-style: italic;
|
||||
font-weight: 900;
|
||||
color: #f3de1e;
|
||||
background: linear-gradient(to right, #f3de1e, #ffffff);
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
`
|
||||
const Header = styled.h3`
|
||||
margin: 0;
|
||||
padding: 20px 20px 0;
|
||||
`
|
||||
const Body = styled.p`
|
||||
line-height: 143%;
|
||||
margin: 16px 20px 31px;
|
||||
`
|
||||
const LinkOutCircle = styled(ArrowDownCircle)`
|
||||
transform: rotate(230deg);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
`
|
||||
const LinkOutToBridge = styled.a`
|
||||
align-items: center;
|
||||
background-color: black;
|
||||
border-radius: 16px;
|
||||
color: white;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 0 18px 18px 18px;
|
||||
padding: 14px 24px;
|
||||
text-decoration: none;
|
||||
width: auto;
|
||||
:hover,
|
||||
:focus,
|
||||
:active {
|
||||
background-color: black;
|
||||
}
|
||||
`
|
||||
export function SwapNetworkAlert() {
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
const [arbitrumAlphaAcknowledged, setArbitrumAlphaAcknowledged] = useArbitrumAlphaAlert()
|
||||
const [locallyDismissed, setLocallyDimissed] = useState(false)
|
||||
const userEthBalance = useETHBalances(account ? [account] : [])?.[account ?? '']
|
||||
|
||||
const dismiss = useCallback(() => {
|
||||
if (userEthBalance?.greaterThan(0)) {
|
||||
setArbitrumAlphaAcknowledged(true)
|
||||
} else {
|
||||
setLocallyDimissed(true)
|
||||
}
|
||||
}, [setArbitrumAlphaAcknowledged, userEthBalance])
|
||||
if (chainId !== SupportedChainId.ARBITRUM_ONE || arbitrumAlphaAcknowledged || locallyDismissed) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<Wrapper>
|
||||
<CloseIcon onClick={dismiss} />
|
||||
<Header>
|
||||
<Trans>
|
||||
Uniswap on <ArbitrumTextStyles>Arbitrum</ArbitrumTextStyles>
|
||||
</Trans>
|
||||
</Header>
|
||||
<Body>
|
||||
<Trans>
|
||||
This is an alpha release of Uniswap on the Arbitrum network. You must bridge L1 assets to the network to swap
|
||||
them.
|
||||
</Trans>
|
||||
</Body>
|
||||
<LinkOutToBridge href="https://bridge.arbitrum.io/" target="_blank" rel="noopener noreferrer">
|
||||
<Trans>Deposit to Arbitrum</Trans>
|
||||
<LinkOutCircle />
|
||||
</LinkOutToBridge>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { Trade as V3Trade, FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { Fragment, memo, useContext } from 'react'
|
||||
import { ChevronRight } from 'react-feather'
|
||||
import { Flex } from 'rebass'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { ThemeContext } from 'styled-components/macro'
|
||||
import { TYPE } from '../../theme'
|
||||
import { unwrappedToken } from 'utils/unwrappedToken'
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useCallback } from 'react'
|
||||
import { Price, Currency } from '@uniswap/sdk-core'
|
||||
import { useContext } from 'react'
|
||||
import { Text } from 'rebass'
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
|
||||
interface TradePriceProps {
|
||||
price: Price<Currency, Currency>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { transparentize } from 'polished'
|
||||
import { ReactNode } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import styled, { css } from 'styled-components'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
import { Text } from 'rebass'
|
||||
import { AutoColumn } from '../Column'
|
||||
|
||||
@@ -42,11 +41,6 @@ export const SectionBreak = styled.div`
|
||||
background-color: ${({ theme }) => theme.bg3};
|
||||
`
|
||||
|
||||
export const BottomGrouping = styled.div`
|
||||
margin-top: ;
|
||||
/* background-color: ${({ theme }) => theme.bg1}; */
|
||||
`
|
||||
|
||||
export const ErrorText = styled(Text)<{ severity?: 0 | 1 | 2 | 3 | 4 }>`
|
||||
color: ${({ theme, severity }) =>
|
||||
severity === 3 || severity === 4
|
||||
@@ -58,55 +52,6 @@ export const ErrorText = styled(Text)<{ severity?: 0 | 1 | 2 | 3 | 4 }>`
|
||||
: theme.text2};
|
||||
`
|
||||
|
||||
export const ErrorPill = styled(Text)<{ severity?: 0 | 1 | 2 | 3 | 4 }>`
|
||||
border-radius: 8px;
|
||||
|
||||
color: ${({ theme, severity }) =>
|
||||
severity === 3 || severity === 4
|
||||
? theme.red1
|
||||
: severity === 2
|
||||
? theme.yellow2
|
||||
: severity === 1
|
||||
? theme.text1
|
||||
: theme.text3};
|
||||
|
||||
/* background-color: ${({ theme, severity }) =>
|
||||
severity === 3 || severity === 4
|
||||
? transparentize(0.9, theme.red1)
|
||||
: severity === 2
|
||||
? transparentize(0.9, theme.yellow2)
|
||||
: severity === 1
|
||||
? transparentize(0.9, theme.text1)
|
||||
: transparentize(0.9, theme.green1)}; */
|
||||
`
|
||||
|
||||
export const StyledBalanceMaxMini = styled.button`
|
||||
/* height: 22px; */
|
||||
width: fit-content;
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 0;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 400;
|
||||
opacity: 0.6;
|
||||
margin-right: 0.5rem;
|
||||
cursor: pointer;
|
||||
color: ${({ theme }) => theme.text1};
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
float: right;
|
||||
|
||||
:hover {
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
}
|
||||
:focus {
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
outline: none;
|
||||
}
|
||||
`
|
||||
|
||||
export const TruncatedText = styled(Text)`
|
||||
text-overflow: ellipsis;
|
||||
max-width: 220px;
|
||||
@@ -183,19 +128,3 @@ export const SwapShowAcceptChanges = styled(AutoColumn)`
|
||||
border-radius: 12px;
|
||||
margin-top: 8px;
|
||||
`
|
||||
export const Separator = styled.div`
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
`
|
||||
|
||||
export const V2TradeAlertWrapper = styled(Link)`
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 12px;
|
||||
height: 22px;
|
||||
margin-right: 0.5rem;
|
||||
padding: 0 0.25rem 0 0.5rem;
|
||||
text-decoration: none !important;
|
||||
`
|
||||
|
||||
60
src/components/vote/ProposalEmptyState.tsx
Normal file
60
src/components/vote/ProposalEmptyState.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { L2_CHAIN_IDS } from 'constants/chains'
|
||||
import { useActiveWeb3React } from 'hooks/web3'
|
||||
import styled from 'styled-components/macro'
|
||||
import { TYPE } from 'theme'
|
||||
|
||||
const EmptyProposals = styled.div`
|
||||
border: 1px solid ${({ theme }) => theme.text4};
|
||||
padding: 16px 12px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`
|
||||
const Sub = styled.i`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
`
|
||||
interface EmptyStateProps {
|
||||
HeaderContent: () => JSX.Element
|
||||
SubHeaderContent: () => JSX.Element
|
||||
}
|
||||
const EmptyState = ({ HeaderContent, SubHeaderContent }: EmptyStateProps) => (
|
||||
<EmptyProposals>
|
||||
<TYPE.body style={{ marginBottom: '8px' }}>
|
||||
<HeaderContent />
|
||||
</TYPE.body>
|
||||
<TYPE.subHeader>
|
||||
<Sub>
|
||||
<SubHeaderContent />
|
||||
</Sub>
|
||||
</TYPE.subHeader>
|
||||
</EmptyProposals>
|
||||
)
|
||||
|
||||
export default function ProposalEmptyState() {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
if (chainId && L2_CHAIN_IDS.includes(chainId)) {
|
||||
return (
|
||||
<EmptyState
|
||||
HeaderContent={() => <Trans>Please connect to Layer 1 Ethereum</Trans>}
|
||||
SubHeaderContent={() => (
|
||||
<Trans>
|
||||
Uniswap governance is only available on Layer 1. Switch your network to Ethereum Mainnet to view Proposals
|
||||
and Vote.
|
||||
</Trans>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<EmptyState
|
||||
HeaderContent={() => <Trans>No proposals found.</Trans>}
|
||||
SubHeaderContent={() => <Trans>Proposals submitted by community members will appear here.</Trans>}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { getExplorerLink, ExplorerDataType } from '../../utils/getExplorerLink'
|
||||
|
||||
import Modal from '../Modal'
|
||||
import { AutoColumn, ColumnCenter } from '../Column'
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
import { RowBetween } from '../Row'
|
||||
import { TYPE, CustomLightSpinner } from '../../theme'
|
||||
import { X, ArrowUpCircle } from 'react-feather'
|
||||
@@ -13,7 +13,6 @@ import Circle from '../../assets/images/blue-loader.svg'
|
||||
import { useVoteCallback, useUserVotes } from '../../state/governance/hooks'
|
||||
import { ExternalLink } from '../../theme/components'
|
||||
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
||||
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { Trans } from '@lingui/macro'
|
||||
|
||||
const ContentWrapper = styled(AutoColumn)`
|
||||
@@ -50,7 +49,7 @@ export default function VoteModal({ isOpen, onDismiss, proposalId, support }: Vo
|
||||
}: {
|
||||
voteCallback: (proposalId: string | undefined, support: boolean) => Promise<string> | undefined
|
||||
} = useVoteCallback()
|
||||
const availableVotes: CurrencyAmount<Token> | undefined = useUserVotes()
|
||||
const { votes: availableVotes } = useUserVotes()
|
||||
|
||||
// monitor call to help UI loading state
|
||||
const [hash, setHash] = useState<string | undefined>()
|
||||
|
||||
@@ -28,7 +28,7 @@ interface BatchItem {
|
||||
reject: (error: Error) => void
|
||||
}
|
||||
|
||||
export class MiniRpcProvider implements AsyncSendable {
|
||||
class MiniRpcProvider implements AsyncSendable {
|
||||
public readonly isMetaMask: false = false
|
||||
public readonly chainId: number
|
||||
public readonly url: string
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { Web3Provider } from '@ethersproject/providers'
|
||||
import { InjectedConnector } from '@web3-react/injected-connector'
|
||||
import { PortisConnector } from '@web3-react/portis-connector'
|
||||
import { WalletConnectConnector } from '@web3-react/walletconnect-connector'
|
||||
import { WalletLinkConnector } from '@web3-react/walletlink-connector'
|
||||
import { PortisConnector } from '@web3-react/portis-connector'
|
||||
import UNISWAP_LOGO_URL from '../assets/svg/logo.svg'
|
||||
import { SupportedChainId } from '../constants/chains'
|
||||
import getLibrary from '../utils/getLibrary'
|
||||
|
||||
import { FortmaticConnector } from './Fortmatic'
|
||||
import { NetworkConnector } from './NetworkConnector'
|
||||
import UNISWAP_LOGO_URL from '../assets/svg/logo.svg'
|
||||
|
||||
const INFURA_KEY = process.env.REACT_APP_INFURA_KEY
|
||||
const FORMATIC_KEY = process.env.REACT_APP_FORTMATIC_KEY
|
||||
@@ -19,24 +18,26 @@ if (typeof INFURA_KEY === 'undefined') {
|
||||
throw new Error(`REACT_APP_INFURA_KEY must be a defined environment variable`)
|
||||
}
|
||||
|
||||
const NETWORK_URLS: {
|
||||
[chainId in SupportedChainId]: string
|
||||
} = {
|
||||
const NETWORK_URLS = {
|
||||
[SupportedChainId.MAINNET]: `https://mainnet.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.RINKEBY]: `https://rinkeby.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.ROPSTEN]: `https://ropsten.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.GOERLI]: `https://goerli.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.KOVAN]: `https://kovan.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.ARBITRUM_ONE]: `https://arb1.arbitrum.io/rpc`,
|
||||
[SupportedChainId.ARBITRUM_RINKEBY]: `https://rinkeby.arbitrum.io/rpc`,
|
||||
[SupportedChainId.OPTIMISM]: `https://optimism-mainnet.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.OPTIMISTIC_KOVAN]: `https://optimism-kovan.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.ARBITRUM_ONE]: `https://arbitrum-mainnet.infura.io/v3/${INFURA_KEY}`,
|
||||
[SupportedChainId.ARBITRUM_RINKEBY]: `https://arbitrum-rinkeby.infura.io/v3/${INFURA_KEY}`,
|
||||
}
|
||||
|
||||
const SUPPORTED_CHAIN_IDS: SupportedChainId[] = [
|
||||
const SUPPORTED_CHAIN_IDS = [
|
||||
SupportedChainId.MAINNET,
|
||||
SupportedChainId.KOVAN,
|
||||
SupportedChainId.GOERLI,
|
||||
SupportedChainId.RINKEBY,
|
||||
SupportedChainId.ROPSTEN,
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.OPTIMISTIC_KOVAN,
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
SupportedChainId.ARBITRUM_RINKEBY,
|
||||
]
|
||||
@@ -77,7 +78,7 @@ export const portis = new PortisConnector({
|
||||
|
||||
// mainnet only
|
||||
export const walletlink = new WalletLinkConnector({
|
||||
url: NETWORK_URLS[1],
|
||||
url: NETWORK_URLS[SupportedChainId.MAINNET],
|
||||
appName: 'Uniswap',
|
||||
appLogoUrl: UNISWAP_LOGO_URL,
|
||||
})
|
||||
|
||||
@@ -6,24 +6,28 @@ import { SupportedChainId } from './chains'
|
||||
type AddressMap = { [chainId: number]: string }
|
||||
|
||||
export const UNI_ADDRESS: AddressMap = constructSameAddressMap('0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984')
|
||||
export const MULTICALL2_ADDRESSES: AddressMap = {
|
||||
...constructSameAddressMap('0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696'),
|
||||
[SupportedChainId.ARBITRUM_ONE]: '0x021CeAC7e681dBCE9b5039d2535ED97590eB395c',
|
||||
[SupportedChainId.ARBITRUM_RINKEBY]: '0x334f67349c1cB3A8fF1268c3eC43FF1D3De246C6',
|
||||
export const MULTICALL_ADDRESS: AddressMap = {
|
||||
...constructSameAddressMap('0x1F98415757620B543A52E61c46B32eB19261F984', [SupportedChainId.OPTIMISTIC_KOVAN]),
|
||||
[SupportedChainId.OPTIMISM]: '0x90f872b3d8f33f305e0250db6A2761B354f7710A',
|
||||
[SupportedChainId.ARBITRUM_ONE]: '0xadF885960B47eA2CD9B55E6DAc6B42b7Cb2806dB',
|
||||
[SupportedChainId.ARBITRUM_RINKEBY]: '0xa501c031958F579dB7676fF1CE78AD305794d579',
|
||||
}
|
||||
export const V2_FACTORY_ADDRESSES: AddressMap = constructSameAddressMap(V2_FACTORY_ADDRESS)
|
||||
export const V2_ROUTER_ADDRESS: AddressMap = constructSameAddressMap('0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D')
|
||||
|
||||
// most current governance contract address should always be the 0 index
|
||||
// only support governance on mainnet
|
||||
export const GOVERNANCE_ADDRESSES: AddressMap[] = [
|
||||
{
|
||||
[SupportedChainId.MAINNET]: '0xC4e172459f1E7939D522503B81AFAaC1014CE6F6',
|
||||
},
|
||||
{
|
||||
[SupportedChainId.MAINNET]: '0x5e4be8Bc9637f0EAA1A755019e06A68ce081D58F',
|
||||
},
|
||||
]
|
||||
/**
|
||||
* The older V0 governance account
|
||||
*/
|
||||
export const GOVERNANCE_ALPHA_V0_ADDRESSES: AddressMap = constructSameAddressMap(
|
||||
'0x5e4be8Bc9637f0EAA1A755019e06A68ce081D58F'
|
||||
)
|
||||
/**
|
||||
* The latest governor alpha that is currently admin of timelock
|
||||
*/
|
||||
export const GOVERNANCE_ALPHA_V1_ADDRESSES: AddressMap = {
|
||||
[SupportedChainId.MAINNET]: '0xC4e172459f1E7939D522503B81AFAaC1014CE6F6',
|
||||
}
|
||||
|
||||
export const TIMELOCK_ADDRESS: AddressMap = constructSameAddressMap('0x1a9C8182C09F50C8318d769245beA52c32BE35BC')
|
||||
|
||||
export const MERKLE_DISTRIBUTOR_ADDRESS: AddressMap = {
|
||||
@@ -33,16 +37,25 @@ export const ARGENT_WALLET_DETECTOR_ADDRESS: AddressMap = {
|
||||
[SupportedChainId.MAINNET]: '0xeca4B0bDBf7c55E9b7925919d03CbF8Dc82537E8',
|
||||
}
|
||||
export const V3_CORE_FACTORY_ADDRESSES: AddressMap = constructSameAddressMap(V3_FACTORY_ADDRESS, [
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.OPTIMISTIC_KOVAN,
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
SupportedChainId.ARBITRUM_RINKEBY,
|
||||
])
|
||||
export const QUOTER_ADDRESSES: AddressMap = constructSameAddressMap('0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6', [
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.OPTIMISTIC_KOVAN,
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
SupportedChainId.ARBITRUM_RINKEBY,
|
||||
])
|
||||
export const NONFUNGIBLE_POSITION_MANAGER_ADDRESSES: AddressMap = constructSameAddressMap(
|
||||
'0xC36442b4a4522E871399CD717aBDD847Ab11FE88',
|
||||
[SupportedChainId.ARBITRUM_ONE, SupportedChainId.ARBITRUM_RINKEBY]
|
||||
[
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.OPTIMISTIC_KOVAN,
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
SupportedChainId.ARBITRUM_RINKEBY,
|
||||
]
|
||||
)
|
||||
export const ENS_REGISTRAR_ADDRESSES: AddressMap = {
|
||||
[SupportedChainId.MAINNET]: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e',
|
||||
@@ -54,6 +67,8 @@ export const SOCKS_CONTROLLER_ADDRESSES: AddressMap = {
|
||||
[SupportedChainId.MAINNET]: '0x65770b5283117639760beA3F867b69b3697a91dd',
|
||||
}
|
||||
export const SWAP_ROUTER_ADDRESSES: AddressMap = constructSameAddressMap('0xE592427A0AEce92De3Edee1F18E0157C05861564', [
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.OPTIMISTIC_KOVAN,
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
SupportedChainId.ARBITRUM_RINKEBY,
|
||||
])
|
||||
|
||||
@@ -1,13 +1,32 @@
|
||||
import arbitrumLogoUrl from 'assets/svg/arbitrum_logo.svg'
|
||||
import optimismLogoUrl from 'assets/svg/optimism_logo.svg'
|
||||
|
||||
export enum SupportedChainId {
|
||||
MAINNET = 1,
|
||||
ROPSTEN = 3,
|
||||
RINKEBY = 4,
|
||||
GOERLI = 5,
|
||||
KOVAN = 42,
|
||||
|
||||
ARBITRUM_ONE = 42161,
|
||||
ARBITRUM_RINKEBY = 421611,
|
||||
OPTIMISM = 10,
|
||||
OPTIMISTIC_KOVAN = 69,
|
||||
}
|
||||
|
||||
export type SupportedL2ChainId =
|
||||
| SupportedChainId.ARBITRUM_ONE
|
||||
| SupportedChainId.ARBITRUM_RINKEBY
|
||||
| SupportedChainId.OPTIMISM
|
||||
| SupportedChainId.OPTIMISTIC_KOVAN
|
||||
|
||||
export type SupportedL1ChainId =
|
||||
| SupportedChainId.MAINNET
|
||||
| SupportedChainId.ROPSTEN
|
||||
| SupportedChainId.RINKEBY
|
||||
| SupportedChainId.GOERLI
|
||||
| SupportedChainId.KOVAN
|
||||
|
||||
export const NETWORK_LABELS: { [chainId in SupportedChainId | number]: string } = {
|
||||
[SupportedChainId.MAINNET]: 'Mainnet',
|
||||
[SupportedChainId.RINKEBY]: 'Rinkeby',
|
||||
@@ -15,5 +34,100 @@ export const NETWORK_LABELS: { [chainId in SupportedChainId | number]: string }
|
||||
[SupportedChainId.GOERLI]: 'Görli',
|
||||
[SupportedChainId.KOVAN]: 'Kovan',
|
||||
[SupportedChainId.ARBITRUM_ONE]: 'Arbitrum',
|
||||
[SupportedChainId.ARBITRUM_RINKEBY]: 'Arbitrum Testnet',
|
||||
[SupportedChainId.ARBITRUM_RINKEBY]: 'Arbitrum Rinkeby',
|
||||
[SupportedChainId.OPTIMISM]: 'Optimism',
|
||||
[SupportedChainId.OPTIMISTIC_KOVAN]: 'Optimistic Kovan',
|
||||
} as const
|
||||
|
||||
export const L1_CHAIN_IDS = [
|
||||
SupportedChainId.MAINNET,
|
||||
SupportedChainId.ROPSTEN,
|
||||
SupportedChainId.RINKEBY,
|
||||
SupportedChainId.GOERLI,
|
||||
SupportedChainId.KOVAN,
|
||||
]
|
||||
|
||||
export const L2_CHAIN_IDS = [
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
SupportedChainId.ARBITRUM_RINKEBY,
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.OPTIMISTIC_KOVAN,
|
||||
]
|
||||
interface L1ChainInfo {
|
||||
docs: string
|
||||
explorer: string
|
||||
infoLink: string
|
||||
label: string
|
||||
}
|
||||
interface L2ChainInfo extends L1ChainInfo {
|
||||
bridge: string
|
||||
logoUrl: string
|
||||
}
|
||||
|
||||
type ChainInfo = { [chainId in SupportedL2ChainId]: L2ChainInfo } &
|
||||
{ [chainId in SupportedL1ChainId]: L1ChainInfo } & { [chainId: number]: L1ChainInfo | L2ChainInfo }
|
||||
|
||||
export const CHAIN_INFO: ChainInfo = {
|
||||
[SupportedChainId.ARBITRUM_ONE]: {
|
||||
bridge: 'https://bridge.arbitrum.io/',
|
||||
docs: 'https://offchainlabs.com/',
|
||||
explorer: 'https://explorer.arbitrum.io/',
|
||||
infoLink: 'https://info.uniswap.org/#/arbitrum',
|
||||
label: NETWORK_LABELS[SupportedChainId.ARBITRUM_ONE],
|
||||
logoUrl: arbitrumLogoUrl,
|
||||
},
|
||||
[SupportedChainId.ARBITRUM_RINKEBY]: {
|
||||
bridge: 'https://bridge.arbitrum.io/',
|
||||
docs: 'https://offchainlabs.com/',
|
||||
explorer: 'https://explorer.arbitrum.io/',
|
||||
infoLink: 'https://info.uniswap.org/#/arbitrum/',
|
||||
label: NETWORK_LABELS[SupportedChainId.ARBITRUM_RINKEBY],
|
||||
logoUrl: arbitrumLogoUrl,
|
||||
},
|
||||
[SupportedChainId.MAINNET]: {
|
||||
docs: 'https://docs.uniswap.org/',
|
||||
explorer: 'https://etherscan.io/',
|
||||
infoLink: 'https://info.uniswap.org/#/',
|
||||
label: NETWORK_LABELS[SupportedChainId.MAINNET],
|
||||
},
|
||||
[SupportedChainId.RINKEBY]: {
|
||||
docs: 'https://docs.uniswap.org/',
|
||||
explorer: 'https://rinkeby.etherscan.io/',
|
||||
infoLink: 'https://info.uniswap.org/#/',
|
||||
label: NETWORK_LABELS[SupportedChainId.RINKEBY],
|
||||
},
|
||||
[SupportedChainId.ROPSTEN]: {
|
||||
docs: 'https://docs.uniswap.org/',
|
||||
explorer: 'https://ropsten.etherscan.io/',
|
||||
infoLink: 'https://info.uniswap.org/#/',
|
||||
label: NETWORK_LABELS[SupportedChainId.ROPSTEN],
|
||||
},
|
||||
[SupportedChainId.KOVAN]: {
|
||||
docs: 'https://docs.uniswap.org/',
|
||||
explorer: 'https://kovan.etherscan.io/',
|
||||
infoLink: 'https://info.uniswap.org/#/',
|
||||
label: NETWORK_LABELS[SupportedChainId.KOVAN],
|
||||
},
|
||||
[SupportedChainId.GOERLI]: {
|
||||
docs: 'https://docs.uniswap.org/',
|
||||
explorer: 'https://goerli.etherscan.io/',
|
||||
infoLink: 'https://info.uniswap.org/#/',
|
||||
label: NETWORK_LABELS[SupportedChainId.GOERLI],
|
||||
},
|
||||
[SupportedChainId.OPTIMISM]: {
|
||||
bridge: 'https://gateway.optimism.io/',
|
||||
docs: 'https://optimism.io/',
|
||||
explorer: 'https://optimistic.etherscan.io/',
|
||||
infoLink: 'https://info.uniswap.org/#/optimism/',
|
||||
label: NETWORK_LABELS[SupportedChainId.OPTIMISM],
|
||||
logoUrl: optimismLogoUrl,
|
||||
},
|
||||
[SupportedChainId.OPTIMISTIC_KOVAN]: {
|
||||
bridge: 'https://gateway.optimism.io/',
|
||||
docs: 'https://optimism.io/',
|
||||
explorer: 'https://optimistic.etherscan.io/',
|
||||
infoLink: 'https://info.uniswap.org/#/optimism',
|
||||
label: NETWORK_LABELS[SupportedChainId.OPTIMISTIC_KOVAN],
|
||||
logoUrl: optimismLogoUrl,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,23 +1,17 @@
|
||||
import { GOVERNANCE_ADDRESSES, TIMELOCK_ADDRESS, UNI_ADDRESS } from './addresses'
|
||||
import {
|
||||
GOVERNANCE_ALPHA_V0_ADDRESSES,
|
||||
GOVERNANCE_ALPHA_V1_ADDRESSES,
|
||||
TIMELOCK_ADDRESS,
|
||||
UNI_ADDRESS,
|
||||
} from './addresses'
|
||||
import { SupportedChainId } from './chains'
|
||||
|
||||
// returns { [address]: `Governance (V${n})`} for each address in GOVERNANCE_ADDRESSES except the current, which gets no version indicator
|
||||
const governanceContracts = (): Record<string, string> =>
|
||||
GOVERNANCE_ADDRESSES.reduce(
|
||||
(acc, addressMap, i) => ({
|
||||
...acc,
|
||||
[addressMap[SupportedChainId.MAINNET]]: `Governance${
|
||||
i === 0 ? '' : ` (V${GOVERNANCE_ADDRESSES.length - 1 - i})`
|
||||
}`,
|
||||
}),
|
||||
{}
|
||||
)
|
||||
|
||||
export const COMMON_CONTRACT_NAMES: Record<number, { [address: string]: string }> = {
|
||||
[SupportedChainId.MAINNET]: {
|
||||
[UNI_ADDRESS[SupportedChainId.MAINNET]]: 'UNI',
|
||||
[TIMELOCK_ADDRESS[SupportedChainId.MAINNET]]: 'Timelock',
|
||||
...governanceContracts(),
|
||||
[GOVERNANCE_ALPHA_V0_ADDRESSES[SupportedChainId.MAINNET]]: 'Governance (V0)',
|
||||
[GOVERNANCE_ALPHA_V1_ADDRESSES[SupportedChainId.MAINNET]]: 'Governance',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,36 +1,33 @@
|
||||
// used to mark unsupported tokens, these are hosted lists of unsupported tokens
|
||||
|
||||
const COMPOUND_LIST = 'https://raw.githubusercontent.com/compound-finance/token-list/master/compound.tokenlist.json'
|
||||
const UMA_LIST = 'https://umaproject.org/uma.tokenlist.json'
|
||||
const AAVE_LIST = 'tokenlist.aave.eth'
|
||||
const SYNTHETIX_LIST = 'synths.snx.eth'
|
||||
const WRAPPED_LIST = 'wrapped.tokensoft.eth'
|
||||
const SET_LIST = 'https://raw.githubusercontent.com/SetProtocol/uniswap-tokenlist/main/set.tokenlist.json'
|
||||
const OPYN_LIST = 'https://raw.githubusercontent.com/opynfinance/opyn-tokenlist/master/opyn-v1.tokenlist.json'
|
||||
const ROLL_LIST = 'https://app.tryroll.com/tokens.json'
|
||||
const COINGECKO_LIST = 'https://tokens.coingecko.com/uniswap/all.json'
|
||||
const BA_LIST = 'https://raw.githubusercontent.com/The-Blockchain-Association/sec-notice-list/master/ba-sec-list.json'
|
||||
const CMC_ALL_LIST = 'defi.cmc.eth'
|
||||
const CMC_STABLECOIN = 'stablecoin.cmc.eth'
|
||||
const KLEROS_LIST = 't2crtokens.eth'
|
||||
const COINGECKO_LIST = 'https://tokens.coingecko.com/uniswap/all.json'
|
||||
const COMPOUND_LIST = 'https://raw.githubusercontent.com/compound-finance/token-list/master/compound.tokenlist.json'
|
||||
const GEMINI_LIST = 'https://www.gemini.com/uniswap/manifest.json'
|
||||
const BA_LIST = 'https://raw.githubusercontent.com/The-Blockchain-Association/sec-notice-list/master/ba-sec-list.json'
|
||||
const KLEROS_LIST = 't2crtokens.eth'
|
||||
export const OPTIMISM_LIST = 'https://static.optimism.io/optimism.tokenlist.json'
|
||||
const ROLL_LIST = 'https://app.tryroll.com/tokens.json'
|
||||
const SET_LIST = 'https://raw.githubusercontent.com/SetProtocol/uniswap-tokenlist/main/set.tokenlist.json'
|
||||
const UMA_LIST = 'https://umaproject.org/uma.tokenlist.json'
|
||||
const WRAPPED_LIST = 'wrapped.tokensoft.eth'
|
||||
|
||||
// used to mark unsupported tokens, these are hosted lists of unsupported tokens
|
||||
export const UNSUPPORTED_LIST_URLS: string[] = [BA_LIST]
|
||||
|
||||
// lower index == higher priority for token import
|
||||
export const DEFAULT_LIST_OF_LISTS: string[] = [
|
||||
COMPOUND_LIST,
|
||||
AAVE_LIST,
|
||||
SYNTHETIX_LIST,
|
||||
CMC_ALL_LIST,
|
||||
CMC_STABLECOIN,
|
||||
UMA_LIST,
|
||||
WRAPPED_LIST,
|
||||
SET_LIST,
|
||||
OPYN_LIST,
|
||||
ROLL_LIST,
|
||||
COINGECKO_LIST,
|
||||
CMC_ALL_LIST,
|
||||
CMC_STABLECOIN,
|
||||
KLEROS_LIST,
|
||||
OPTIMISM_LIST,
|
||||
GEMINI_LIST,
|
||||
...UNSUPPORTED_LIST_URLS, // need to load unsupported tokens as well
|
||||
]
|
||||
|
||||
@@ -7,6 +7,7 @@ export const NetworkContextName = 'NETWORK'
|
||||
|
||||
// 30 minutes, denominated in seconds
|
||||
export const DEFAULT_DEADLINE_FROM_NOW = 60 * 30
|
||||
export const L2_DEADLINE_FROM_NOW = 60 * 5
|
||||
|
||||
// used for rewards deadlines
|
||||
export const BIG_INT_SECONDS_IN_WEEK = JSBI.BigInt(60 * 60 * 24 * 7)
|
||||
@@ -14,8 +15,9 @@ export const BIG_INT_SECONDS_IN_WEEK = JSBI.BigInt(60 * 60 * 24 * 7)
|
||||
export const BIG_INT_ZERO = JSBI.BigInt(0)
|
||||
|
||||
// one basis JSBI.BigInt
|
||||
export const ONE_BIPS = new Percent(JSBI.BigInt(1), JSBI.BigInt(10000))
|
||||
export const BIPS_BASE = JSBI.BigInt(10000)
|
||||
const BIPS_BASE = JSBI.BigInt(10000)
|
||||
export const ONE_BIPS = new Percent(JSBI.BigInt(1), BIPS_BASE)
|
||||
|
||||
// used for warning states
|
||||
export const ALLOWED_PRICE_IMPACT_LOW: Percent = new Percent(JSBI.BigInt(100), BIPS_BASE) // 1%
|
||||
export const ALLOWED_PRICE_IMPACT_MEDIUM: Percent = new Percent(JSBI.BigInt(300), BIPS_BASE) // 3%
|
||||
@@ -25,8 +27,7 @@ export const PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN: Percent = new Percent(JSBI.Bi
|
||||
// for non expert mode disable swaps above this
|
||||
export const BLOCKED_PRICE_IMPACT_NON_EXPERT: Percent = new Percent(JSBI.BigInt(1500), BIPS_BASE) // 15%
|
||||
|
||||
// used to ensure the user doesn't send so much ETH so they end up with <.01
|
||||
export const BETTER_TRADE_LESS_HOPS_THRESHOLD = new Percent(JSBI.BigInt(50), JSBI.BigInt(10000))
|
||||
export const BETTER_TRADE_LESS_HOPS_THRESHOLD = new Percent(JSBI.BigInt(50), BIPS_BASE)
|
||||
|
||||
export const ZERO_PERCENT = new Percent('0')
|
||||
export const ONE_HUNDRED_PERCENT = new Percent('1')
|
||||
|
||||
@@ -49,26 +49,23 @@ const mAssetsAdditionalBases: { [tokenAddress: string]: Token[] } = {
|
||||
'0x31c63146a635EB7465e5853020b39713AC356991': [MIR, UST], // mUSO
|
||||
'0xf72FCd9DCF0190923Fadd44811E240Ef4533fc86': [MIR, UST], // mVIXY
|
||||
}
|
||||
const WETH_ONLY: ChainTokenList = {
|
||||
[SupportedChainId.MAINNET]: [WETH9_EXTENDED[SupportedChainId.MAINNET]],
|
||||
[SupportedChainId.ROPSTEN]: [WETH9_EXTENDED[SupportedChainId.ROPSTEN]],
|
||||
[SupportedChainId.RINKEBY]: [WETH9_EXTENDED[SupportedChainId.RINKEBY]],
|
||||
[SupportedChainId.GOERLI]: [WETH9_EXTENDED[SupportedChainId.GOERLI]],
|
||||
[SupportedChainId.KOVAN]: [WETH9_EXTENDED[SupportedChainId.KOVAN]],
|
||||
[SupportedChainId.ARBITRUM_ONE]: [WETH9_EXTENDED[SupportedChainId.ARBITRUM_ONE]],
|
||||
}
|
||||
|
||||
const WETH_ONLY: ChainTokenList = Object.fromEntries(
|
||||
Object.entries(WETH9_EXTENDED).map(([key, value]) => [key, [value]])
|
||||
)
|
||||
|
||||
// used to construct intermediary pairs for trading
|
||||
export const BASES_TO_CHECK_TRADES_AGAINST: ChainTokenList = {
|
||||
...WETH_ONLY,
|
||||
[1]: [...WETH_ONLY[1], DAI, USDC, USDT, WBTC],
|
||||
[SupportedChainId.MAINNET]: [...WETH_ONLY[SupportedChainId.MAINNET], DAI, USDC, USDT, WBTC],
|
||||
}
|
||||
export const ADDITIONAL_BASES: { [chainId: number]: { [tokenAddress: string]: Token[] } } = {
|
||||
[1]: {
|
||||
[SupportedChainId.MAINNET]: {
|
||||
...mAssetsAdditionalBases,
|
||||
'0xF16E4d813f4DcfDe4c5b44f305c908742De84eF0': [ETH2X_FLI],
|
||||
'0xA948E86885e12Fb09AfEF8C52142EBDbDf73cD18': [UNI[1]],
|
||||
'0x561a4717537ff4AF5c687328c0f7E90a319705C0': [UNI[1]],
|
||||
'0xE0360A9e2cdd7d03B9408c7D3001E017BAc2EcD5': [UNI[1]],
|
||||
'0xA948E86885e12Fb09AfEF8C52142EBDbDf73cD18': [UNI[SupportedChainId.MAINNET]],
|
||||
'0x561a4717537ff4AF5c687328c0f7E90a319705C0': [UNI[SupportedChainId.MAINNET]],
|
||||
'0xE0360A9e2cdd7d03B9408c7D3001E017BAc2EcD5': [UNI[SupportedChainId.MAINNET]],
|
||||
'0xa6e3454fec677772dd771788a079355e43910638': [UMA],
|
||||
'0xB46F57e7Ce3a284d74b70447Ef9352B5E5Df8963': [UMA],
|
||||
[FEI.address]: [TRIBE],
|
||||
@@ -84,8 +81,8 @@ export const ADDITIONAL_BASES: { [chainId: number]: { [tokenAddress: string]: To
|
||||
* tokens.
|
||||
*/
|
||||
export const CUSTOM_BASES: { [chainId: number]: { [tokenAddress: string]: Token[] } } = {
|
||||
[1]: {
|
||||
[AMPL.address]: [DAI, WETH9_EXTENDED[1]],
|
||||
[SupportedChainId.MAINNET]: {
|
||||
[AMPL.address]: [DAI, WETH9_EXTENDED[SupportedChainId.MAINNET]],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -93,27 +90,52 @@ export const CUSTOM_BASES: { [chainId: number]: { [tokenAddress: string]: Token[
|
||||
* Shows up in the currency select for swap and add liquidity
|
||||
*/
|
||||
export const COMMON_BASES: ChainCurrencyList = {
|
||||
[1]: [ExtendedEther.onChain(1), DAI, USDC, USDT, WBTC, WETH9_EXTENDED[1]],
|
||||
[3]: [ExtendedEther.onChain(3), WETH9_EXTENDED[3]],
|
||||
[4]: [ExtendedEther.onChain(4), WETH9_EXTENDED[4]],
|
||||
[5]: [ExtendedEther.onChain(5), WETH9_EXTENDED[5]],
|
||||
[42]: [ExtendedEther.onChain(42), WETH9_EXTENDED[42]],
|
||||
[SupportedChainId.MAINNET]: [
|
||||
ExtendedEther.onChain(SupportedChainId.MAINNET),
|
||||
DAI,
|
||||
USDC,
|
||||
USDT,
|
||||
WBTC,
|
||||
WETH9_EXTENDED[SupportedChainId.MAINNET],
|
||||
],
|
||||
[SupportedChainId.ROPSTEN]: [
|
||||
ExtendedEther.onChain(SupportedChainId.ROPSTEN),
|
||||
WETH9_EXTENDED[SupportedChainId.ROPSTEN],
|
||||
],
|
||||
[SupportedChainId.RINKEBY]: [
|
||||
ExtendedEther.onChain(SupportedChainId.RINKEBY),
|
||||
WETH9_EXTENDED[SupportedChainId.RINKEBY],
|
||||
],
|
||||
[SupportedChainId.GOERLI]: [ExtendedEther.onChain(SupportedChainId.GOERLI), WETH9_EXTENDED[SupportedChainId.GOERLI]],
|
||||
[SupportedChainId.KOVAN]: [ExtendedEther.onChain(SupportedChainId.KOVAN), WETH9_EXTENDED[SupportedChainId.KOVAN]],
|
||||
[SupportedChainId.ARBITRUM_ONE]: [
|
||||
ExtendedEther.onChain(SupportedChainId.ARBITRUM_ONE),
|
||||
WETH9_EXTENDED[SupportedChainId.ARBITRUM_ONE],
|
||||
],
|
||||
[SupportedChainId.ARBITRUM_RINKEBY]: [
|
||||
ExtendedEther.onChain(SupportedChainId.ARBITRUM_RINKEBY),
|
||||
WETH9_EXTENDED[SupportedChainId.ARBITRUM_RINKEBY],
|
||||
],
|
||||
[SupportedChainId.OPTIMISM]: [ExtendedEther.onChain(SupportedChainId.OPTIMISM)],
|
||||
[SupportedChainId.OPTIMISTIC_KOVAN]: [ExtendedEther.onChain(SupportedChainId.OPTIMISTIC_KOVAN)],
|
||||
}
|
||||
|
||||
// used to construct the list of all pairs we consider by default in the frontend
|
||||
export const BASES_TO_TRACK_LIQUIDITY_FOR: ChainTokenList = {
|
||||
...WETH_ONLY,
|
||||
[1]: [...WETH_ONLY[1], DAI, USDC, USDT, WBTC],
|
||||
[SupportedChainId.MAINNET]: [...WETH_ONLY[SupportedChainId.MAINNET], DAI, USDC, USDT, WBTC],
|
||||
}
|
||||
export const PINNED_PAIRS: { readonly [chainId: number]: [Token, Token][] } = {
|
||||
[1]: [
|
||||
[SupportedChainId.MAINNET]: [
|
||||
[
|
||||
new Token(1, '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643', 8, 'cDAI', 'Compound Dai'),
|
||||
new Token(1, '0x39AA39c021dfbaE8faC545936693aC917d5E7563', 8, 'cUSDC', 'Compound USD Coin'),
|
||||
new Token(SupportedChainId.MAINNET, '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643', 8, 'cDAI', 'Compound Dai'),
|
||||
new Token(
|
||||
SupportedChainId.MAINNET,
|
||||
'0x39AA39c021dfbaE8faC545936693aC917d5E7563',
|
||||
8,
|
||||
'cUSDC',
|
||||
'Compound USD Coin'
|
||||
),
|
||||
],
|
||||
[USDC, USDT],
|
||||
[DAI, USDT],
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user